From 601ca5cae1e7b4e63bf74e3c73fd1fb0b8771382 Mon Sep 17 00:00:00 2001 From: Joe Tsai Date: Sat, 21 Dec 2024 09:30:02 -0800 Subject: [PATCH] Move diff_test.go to v1 The diff_test.go suite of tests check for expected differences between v1 and v2. In some ways, they are higher fidelity tests for v1 behavior than the v1 tests themselves. Modify diff_test.go to use the emulated v1 implementation and skip the tests are currently failing. --- README.md | 47 ++++++++++++++++----------------- diff_test.go => v1/diff_test.go | 12 ++++++--- v1/failing.txt | 10 +++++++ 3 files changed, 41 insertions(+), 28 deletions(-) rename diff_test.go => v1/diff_test.go (99%) diff --git a/README.md b/README.md index 243e2af..f6997dd 100644 --- a/README.md +++ b/README.md @@ -141,30 +141,29 @@ This table shows an overview of the changes: | v1 | v2 | Details | | -- | -- | ------- | -| 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](/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](/diff_test.go#:~:text=TestOmitEmptyOption) | -| The `string` option **does affect** Go bools. | The `string` option **does not affect** Go bools. | [StringOption](/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](/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](/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](/diff_test.go#:~:text=TestNilSlicesAndMaps) | -| A nil Go map is marshaled as a **JSON null**. | A nil Go map is marshaled as an **empty JSON object**. | [NilSlicesAndMaps](/diff_test.go#:~:text=TestNilSlicesAndMaps) | -| A Go array may be unmarshaled from a **JSON array of any length**. | A Go array must be unmarshaled from a **JSON array of the same length**. | [Arrays](/diff_test.go#:~:text=Arrays) | -| A Go byte array is represented as a **JSON array of JSON numbers**. | A Go byte array is represented as a **Base64-encoded JSON string**. | [ByteArrays](/diff_test.go#:~:text=TestByteArrays) | -| `MarshalJSON` and `UnmarshalJSON` methods declared on a pointer receiver are **inconsistently called**. | `MarshalJSON` and `UnmarshalJSON` methods declared on a pointer receiver are **consistently called**. | [PointerReceiver](/diff_test.go#:~:text=TestPointerReceiver) | -| A Go map is marshaled in a **deterministic order**. | A Go map is marshaled in a **non-deterministic order**. | [MapDeterminism](/diff_test.go#:~:text=TestMapDeterminism) | -| JSON strings are encoded **with HTML-specific characters being escaped**. | JSON strings are encoded **without any characters being escaped** (unless necessary). | [EscapeHTML](/diff_test.go#:~:text=TestEscapeHTML) | -| When marshaling, invalid UTF-8 within a Go string **are silently replaced**. | When marshaling, invalid UTF-8 within a Go string **results in an error**. | [InvalidUTF8](/diff_test.go#:~:text=TestInvalidUTF8) | -| When unmarshaling, invalid UTF-8 within a JSON string **are silently replaced**. | When unmarshaling, invalid UTF-8 within a JSON string **results in an error**. | [InvalidUTF8](/diff_test.go#:~:text=TestInvalidUTF8) | -| When marshaling, **an error does not occur** if the output JSON value contains objects with duplicate names. | When marshaling, **an error does occur** if the output JSON value contains objects with duplicate names. | [DuplicateNames](/diff_test.go#:~:text=TestDuplicateNames) | -| When unmarshaling, **an error does not occur** if the input JSON value contains objects with duplicate names. | When unmarshaling, **an error does occur** if the input JSON value contains objects with duplicate names. | [DuplicateNames](/diff_test.go#:~:text=TestDuplicateNames) | -| Unmarshaling a JSON null into a non-empty Go value **inconsistently clears the value or does nothing**. | Unmarshaling a JSON null into a non-empty Go value **always clears the value**. | [MergeNull](/diff_test.go#:~:text=TestMergeNull) | -| Unmarshaling a JSON value into a non-empty Go value **follows inconsistent and bizarre behavior**. | Unmarshaling a JSON value into a non-empty Go value **always merges if the input is an object, and otherwise replaces**. | [MergeComposite](/diff_test.go#:~:text=TestMergeComposite) | -| A `time.Duration` is represented as a **JSON number containing the decimal number of nanoseconds**. | A `time.Duration` is represented as a **JSON string containing the formatted duration (e.g., "1h2m3.456s")**. | [TimeDurations](/diff_test.go#:~:text=TestTimeDurations) | -| Unmarshaling a JSON number into a Go float beyond its representation **results in an error**. | Unmarshaling a JSON number into a Go float beyond its representation **uses the closest representable value (e.g., ±`math.MaxFloat`)**. | [MaxFloats](/diff_test.go#:~:text=TestMaxFloats) | -| A Go struct with only unexported fields **can be serialized**. | A Go struct with only unexported fields **cannot be serialized**. | [EmptyStructs](/diff_test.go#:~:text=TestEmptyStructs) | -| A Go struct that embeds an unexported struct type **can sometimes be serialized**. | A Go struct that embeds an unexported struct type **cannot be serialized**. | [EmbedUnexported](/diff_test.go#:~:text=TestEmbedUnexported) | - -See [diff_test.go](/diff_test.go) for details about every change. +| 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 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) | +| A nil Go map is marshaled as a **JSON null**. | A nil Go map is marshaled as an **empty JSON object**. | [NilSlicesAndMaps](/v1/diff_test.go#:~:text=TestNilSlicesAndMaps) | +| A Go array may be unmarshaled from a **JSON array of any length**. | A Go array must be unmarshaled from a **JSON array of the same length**. | [Arrays](/v1/diff_test.go#:~:text=Arrays) | +| A Go byte array is represented as a **JSON array of JSON numbers**. | A Go byte array is represented as a **Base64-encoded JSON string**. | [ByteArrays](/v1/diff_test.go#:~:text=TestByteArrays) | +| `MarshalJSON` and `UnmarshalJSON` methods declared on a pointer receiver are **inconsistently called**. | `MarshalJSON` and `UnmarshalJSON` methods declared on a pointer receiver are **consistently called**. | [PointerReceiver](/v1/diff_test.go#:~:text=TestPointerReceiver) | +| A Go map is marshaled in a **deterministic order**. | A Go map is marshaled in a **non-deterministic order**. | [MapDeterminism](/v1/diff_test.go#:~:text=TestMapDeterminism) | +| JSON strings are encoded **with HTML-specific characters being escaped**. | JSON strings are encoded **without any characters being escaped** (unless necessary). | [EscapeHTML](/v1/diff_test.go#:~:text=TestEscapeHTML) | +| When marshaling, invalid UTF-8 within a Go string **are silently replaced**. | When marshaling, invalid UTF-8 within a Go string **results in an error**. | [InvalidUTF8](/v1/diff_test.go#:~:text=TestInvalidUTF8) | +| When unmarshaling, invalid UTF-8 within a JSON string **are silently replaced**. | When unmarshaling, invalid UTF-8 within a JSON string **results in an error**. | [InvalidUTF8](/v1/diff_test.go#:~:text=TestInvalidUTF8) | +| When marshaling, **an error does not occur** if the output JSON value contains objects with duplicate names. | When marshaling, **an error does occur** if the output JSON value contains objects with duplicate names. | [DuplicateNames](/v1/diff_test.go#:~:text=TestDuplicateNames) | +| When unmarshaling, **an error does not occur** if the input JSON value contains objects with duplicate names. | When unmarshaling, **an error does occur** if the input JSON value contains objects with duplicate names. | [DuplicateNames](/v1/diff_test.go#:~:text=TestDuplicateNames) | +| Unmarshaling a JSON null into a non-empty Go value **inconsistently clears the value or does nothing**. | Unmarshaling a JSON null into a non-empty Go value **always clears the value**. | [MergeNull](/v1/diff_test.go#:~:text=TestMergeNull) | +| Unmarshaling a JSON value into a non-empty Go value **follows inconsistent and bizarre behavior**. | Unmarshaling a JSON value into a non-empty Go value **always merges if the input is an object, and otherwise replaces**. | [MergeComposite](/v1/diff_test.go#:~:text=TestMergeComposite) | +| A `time.Duration` is represented as a **JSON number containing the decimal number of nanoseconds**. | A `time.Duration` is represented as a **JSON string containing the formatted duration (e.g., "1h2m3.456s")**. | [TimeDurations](/v1/diff_test.go#:~:text=TestTimeDurations) | +| Unmarshaling a JSON number into a Go float beyond its representation **results in an error**. | Unmarshaling a JSON number into a Go float beyond its representation **uses the closest representable value (e.g., ±`math.MaxFloat`)**. | [MaxFloats](/v1/diff_test.go#:~:text=TestMaxFloats) | +| A Go struct with only unexported fields **can be serialized**. | A Go struct with only unexported fields **cannot be serialized**. | [EmptyStructs](/v1/diff_test.go#:~:text=TestEmptyStructs) | + +See [diff_test.go](/v1/diff_test.go) for details about every change. ## Performance diff --git a/diff_test.go b/v1/diff_test.go similarity index 99% rename from diff_test.go rename to v1/diff_test.go index 2b2ae0e..cd9b0f5 100644 --- a/diff_test.go +++ b/v1/diff_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package json_test +package json import ( "errors" @@ -13,8 +13,6 @@ import ( "testing" "time" - jsonv1 "encoding/json" - jsonv2 "github.com/go-json-experiment/json" ) @@ -27,7 +25,7 @@ var jsonPackages = []struct { Marshal func(any) ([]byte, error) Unmarshal func([]byte, any) error }{ - {"v1", jsonv1.Marshal, jsonv1.Unmarshal}, + {"v1", Marshal, Unmarshal}, {"v2", func(in any) ([]byte, error) { return jsonv2.Marshal(in) }, func(in []byte, out any) error { return jsonv2.Unmarshal(in, out) }}, @@ -367,6 +365,7 @@ func TestStringOption(t *testing.T) { for _, json := range jsonPackages { t.Run(path.Join("Unmarshal/Null", json.Version), func(t *testing.T) { + skipKnownFailure(t) var got Types err := json.Unmarshal([]byte(`{ "Bool": "null", @@ -416,6 +415,7 @@ func TestStringOption(t *testing.T) { }) t.Run(path.Join("Unmarshal/Deep", json.Version), func(t *testing.T) { + skipKnownFailure(t) var got Types want := map[string]Types{ "v1": { @@ -631,6 +631,7 @@ func TestPointerReceiver(t *testing.T) { for _, json := range jsonPackages { t.Run(path.Join("Marshal", json.Version), func(t *testing.T) { + skipKnownFailure(t) var cc CallCheck in := Values{ S: []CallCheck{cc}, @@ -655,6 +656,7 @@ func TestPointerReceiver(t *testing.T) { for _, json := range jsonPackages { t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) { + skipKnownFailure(t) in := `{"S":[""],"A":[""],"M":{"":""},"V":"","I":""}` called := CallCheck("CALLED") // resulting state if UnmarshalJSON is called want := map[string]Values{ @@ -881,6 +883,7 @@ func TestMergeNull(t *testing.T) { for _, json := range jsonPackages { t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) { + skipKnownFailure(t) // Start with a non-empty value where all fields are populated. in := Types{ Bool: true, @@ -960,6 +963,7 @@ func TestMergeComposite(t *testing.T) { for _, json := range jsonPackages { t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) { + skipKnownFailure(t) // Start with a non-empty value where all fields are populated. in := Composites{ Slice: []Tuple{{Old: true}, {Old: true}}[:1], diff --git a/v1/failing.txt b/v1/failing.txt index 95f462a..623a34f 100644 --- a/v1/failing.txt +++ b/v1/failing.txt @@ -24,3 +24,13 @@ TestNilMarshalerTextMapKey TestEncoderSetEscapeHTML TestEncoderSetEscapeHTML/stringOption TestRawMessage +TestStringOption +TestStringOption/Unmarshal/Null/v1 +TestStringOption/Unmarshal/Deep/v1 +TestPointerReceiver +TestPointerReceiver/Marshal/v1 +TestPointerReceiver/Unmarshal/v1 +TestMergeNull +TestMergeNull/Unmarshal/v1 +TestMergeComposite +TestMergeComposite/Unmarshal/v1