Skip to content

Commit

Permalink
Update README.md with how legacy string affect Go strings (#95)
Browse files Browse the repository at this point in the history
  • Loading branch information
dsnet authored Dec 28, 2024
1 parent f92909b commit ac6c32e
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 18 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ This table shows an overview of the changes:
| -- | -- | ------- |
| JSON object members are unmarshaled into a Go struct using a **case-insensitive name match**. | JSON object members are unmarshaled into a Go struct using a **case-sensitive name match**. | [CaseSensitivity](/v1/diff_test.go#:~:text=TestCaseSensitivity) |
| When marshaling a Go struct, a struct field marked as `omitempty` is omitted if **the field value is an empty Go value**, which is defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string. | When marshaling a Go struct, a struct field marked as `omitempty` is omitted if **the field value would encode as an empty JSON value**, which is defined as a JSON null, or an empty JSON string, object, or array. | [OmitEmptyOption](/v1/diff_test.go#:~:text=TestOmitEmptyOption) |
| The `string` option **does affect** Go bools. | The `string` option **does not affect** Go bools. | [StringOption](/v1/diff_test.go#:~:text=TestStringOption) |
| The `string` option **does affect** Go strings and bools. | The `string` option **does not affect** Go strings or bools. | [StringOption](/v1/diff_test.go#:~:text=TestStringOption) |
| The `string` option **does not recursively affect** sub-values of the Go field value. | The `string` option **does recursively affect** sub-values of the Go field value. | [StringOption](/v1/diff_test.go#:~:text=TestStringOption) |
| The `string` option **sometimes accepts** a JSON null escaped within a JSON string. | The `string` option **never accepts** a JSON null escaped within a JSON string. | [StringOption](/v1/diff_test.go#:~:text=TestStringOption) |
| A nil Go slice is marshaled as a **JSON null**. | A nil Go slice is marshaled as an **empty JSON array**. | [NilSlicesAndMaps](/v1/diff_test.go#:~:text=TestNilSlicesAndMaps) |
Expand Down
39 changes: 22 additions & 17 deletions v1/diff_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"time"

jsonv2 "github.com/go-json-experiment/json"
"github.com/go-json-experiment/json/jsontext"
)

// NOTE: This file serves as a list of semantic differences between v1 and v2.
Expand Down Expand Up @@ -248,12 +249,11 @@ func addr[T any](v T) *T {
return &v
}

// In v1, the "string" option specifies that Go bools and numeric values are
// encoded within a JSON string when marshaling and are unmarshaled from
// either the native JSON representation (i.e., a JSON bool or number) or
// its native representation escaped within a JSON string.
// The "string" option is not applied recursively, and
// so does not affect bools and numeric values within a Go slice or map, but
// In v1, the "string" option specifies that Go strings, bools, and numeric
// values are encoded within a JSON string when marshaling and
// are unmarshaled from its native representation escaped within a JSON string.
// The "string" option is not applied recursively, and so does not affect
// strings, bools, and numeric values within a Go slice or map, but
// does have special handling to affect the underlying value within a pointer.
// When unmarshaling, the "string" option permits decoding from a JSON null
// escaped within a JSON string in some inconsistent cases.
Expand All @@ -265,15 +265,14 @@ func addr[T any](v T) *T {
// and thus affects numeric values within a Go slice or map.
// There is no support for escaped JSON nulls within a JSON string.
//
// The main utility for stringifying JSON primitives (i.e., bools and numbers)
// is because JSON parsers often represents numbers as IEEE 754
// floating-point numbers. This results in a loss of precision when trying to
// represent 64-bit integer values. Consequently, many JSON-based APIs actually
// requires that such values be encoded within a JSON string.
// Given the main utility of stringification is for numeric values,
// v2 limits the effect of the "string" option to just numeric Go types.
// According to all code known by the Go module proxy,
// there are close to zero usages of the "string" option with a Go bool.
// The main utility for stringifying JSON numbers is because JSON parsers
// often represents numbers as IEEE 754 floating-point numbers.
// This results in a loss of precision representing 64-bit integer values.
// Consequently, many JSON-based APIs actually requires that such values
// be encoded within a JSON string. Since the main utility of stringification
// is for numeric values, v2 limits the effect of the "string" option
// to just numeric Go types. According to all code known by the Go module proxy,
// there are close to zero usages of the "string" option on a Go string or bool.
//
// Regarding the recursive application of the "string" option,
// there have been a number of issues filed about users being surprised that
Expand All @@ -295,6 +294,7 @@ func addr[T any](v T) *T {
// https://go.dev/issue/50997
func TestStringOption(t *testing.T) {
type Types struct {
String string `json:",string"`
Bool bool `json:",string"`
Int int `json:",string"`
Float float64 `json:",string"`
Expand All @@ -312,6 +312,7 @@ func TestStringOption(t *testing.T) {
for _, json := range jsonPackages {
t.Run(path.Join("Marshal", json.Version), func(t *testing.T) {
in := Types{
String: "string",
Bool: true,
Int: 1,
Float: 1,
Expand All @@ -325,7 +326,10 @@ func TestStringOption(t *testing.T) {
InterfaceA: nil,
InterfaceB: 1,
}
quote := func(s string) string { return `"` + s + `"` }
quote := func(s string) string {
b, _ := jsontext.AppendQuote(nil, s)
return string(b)
}
quoteOnlyV1 := func(s string) string {
if json.Version == "v1" {
s = quote(s)
Expand All @@ -340,7 +344,8 @@ func TestStringOption(t *testing.T) {
}
want := strings.Join([]string{
`{`,
`"Bool":` + quoteOnlyV1("true") + `,`, // in v1, Go bool are also stringified
`"String":` + quoteOnlyV1(`"string"`) + `,`, // in v1, Go strings are also stringified
`"Bool":` + quoteOnlyV1("true") + `,`, // in v1, Go bools are also stringified
`"Int":` + quote("1") + `,`,
`"Float":` + quote("1") + `,`,
`"Map":{"Name":` + quoteOnlyV2("1") + `},`, // in v2, numbers are recursively stringified
Expand Down

0 comments on commit ac6c32e

Please sign in to comment.