From 69fa32ec8e966a0b57a6efd15a9478a542996ca4 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 15 Sep 2021 11:43:37 +0300 Subject: [PATCH 01/67] *: add go.mod and go.sum --- go.mod | 10 ++++++++++ go.sum | 6 ++++++ 2 files changed, 16 insertions(+) create mode 100644 go.mod create mode 100644 go.sum diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..49c47f8 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module github.com/nspcc-dev/go-ordered-json + +go 1.15 + +require ( + github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c +) + +replace github.com/virtuald/go-ordered-json => github.com/nspcc-dev/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7b021a4 --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +github.com/nspcc-dev/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 h1:nNfKXQszRbLcbMVe0t/GeiXxw1ouQcJzzxLlDzB7SvE= +github.com/nspcc-dev/go-ordered-json v0.0.0-20170621173500-b18e6e673d74/go.mod h1:IL5YXjbcudZiFB5Xi27bMlPRWzsm0LkIpszR+PFqTnU= +github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 h1:JwtAtbp7r/7QSyGz8mKUbYJBg2+6Cd7OjM8o/GNOcVo= +github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74/go.mod h1:RmMWU37GKR2s6pgrIEB4ixgpVCt/cf7dnJv3fuH1J1c= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From 593850d3adb3e24cf15d4342501d9b9b6cb5600f Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 15 Sep 2021 12:32:39 +0300 Subject: [PATCH 02/67] README: add note about fork motivations --- README.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c0b78e7..ddf2c3d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -go-ordered-json +nspcc-dev fork of go-ordered-json =============== There are some legacy/stupid applications[1] that you need to interoperate with, @@ -18,4 +18,18 @@ golang. Known broken applications ------------------------- -* [1][Windows Communication Foundation Json __type ordering](https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/stand-alone-json-serialization#type-hint-position-in-json-objects) \ No newline at end of file +* [1][Windows Communication Foundation Json __type ordering](https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/stand-alone-json-serialization#type-hint-position-in-json-objects) +* [2][NEO node](https://github.com/neo-project/neo/tree/master/src/neo/IO/Json) + +Fork motivations +------------------------- + +NEO project has its own implementation of JSON serializer which is more a JSON +dialect rather than standard-compatible implementation. Until JSON serialisation +format affects contract states we need to be byte-to-byte compatible with the +reference JSON serializer. This fork contains the following compatibility quirks: + +* JSON serializer is ordered (see https://github.com/nspcc-dev/neo-go/pull/2026) (implemented in the original [virtuald/go-ordered-json](https://github.com/virtuald/go-ordered-json) repository) +* JSON serializer escapes non-ascii characters while marshalling (see https://github.com/nspcc-dev/neo-go/pull/2174) + +More compatibility quirks may be added in the future. \ No newline at end of file From fed94d8b56607f7df9d5c2f30b794e3483fe44aa Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 15 Sep 2021 12:36:27 +0300 Subject: [PATCH 03/67] github: add Tests github workflow --- .github/workflows/run_tests.yml | 89 +++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 .github/workflows/run_tests.yml diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml new file mode 100644 index 0000000..39b19e3 --- /dev/null +++ b/.github/workflows/run_tests.yml @@ -0,0 +1,89 @@ +name: Tests + +on: + pull_request: + branches: + - master + types: [opened, synchronize] + paths-ignore: + - '*.md' + workflow_dispatch: + +env: + GO111MODULE: "on" + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: golangci-lint + uses: golangci/golangci-lint-action@v2 + with: + version: latest + + test_cover: + name: Coverage + runs-on: ubuntu-18.04 + + env: + CGO_ENABLED: 0 + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.17 + + - name: Restore Go modules from cache + uses: actions/cache@v2 + with: + path: /home/runner/go/pkg/mod + key: deps-${{ hashFiles('go.sum') }} + + - name: Update Go modules + run: go mod download -json + + - name: Write coverage profile + run: go test -v ./... -coverprofile=./coverage.txt -covermode=atomic -coverpkg=./pkg...,./cli/... + + - name: Upload coverage results to Codecov + uses: codecov/codecov-action@v1 + with: + fail_ci_if_error: false + path_to_write_report: ./coverage.txt + verbose: true + + tests: + name: Go + runs-on: ubuntu-18.04 + strategy: + matrix: + go_versions: [ '1.15', '1.16' ] + fail-fast: false + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: '${{ matrix.go_versions }}' + + - name: Restore Go modules from cache + uses: actions/cache@v2 + with: + path: /home/runner/go/pkg/mod + key: deps-${{ hashFiles('go.sum') }} + + - name: Update Go modules + run: go mod download -json + + - name: Run tests + run: go test -v -race ./... \ No newline at end of file From a6680590b53744255c8c4caa29ab3bbcd166a923 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 15 Sep 2021 11:42:18 +0300 Subject: [PATCH 04/67] encode: escape non-ascii characters during encoding --- decode_test.go | 2 +- encode.go | 21 +++++++++++++++++++++ encode_test.go | 13 +++++++++++++ stream_test.go | 6 +++--- 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/decode_test.go b/decode_test.go index 67ab32a..200ab4d 100644 --- a/decode_test.go +++ b/decode_test.go @@ -863,7 +863,7 @@ var badUTF8 = []struct { {"\xff", `"\ufffd"`}, {"\xff\xff", `"\ufffd\ufffd"`}, {"a\xffb", `"a\ufffdb"`}, - {"\xe6\x97\xa5\xe6\x9c\xac\xff\xaa\x9e", `"日本\ufffd\ufffd\ufffd"`}, + {"\xe6\x97\xa5\xe6\x9c\xac\xff\xaa\x9e", `"\u65E5\u672C\ufffd\ufffd\ufffd"`}, } func TestMarshalBadUTF8(t *testing.T) { diff --git a/encode.go b/encode.go index f3e86aa..a871f8c 100644 --- a/encode.go +++ b/encode.go @@ -24,6 +24,7 @@ import ( "sync" "sync/atomic" "unicode" + "unicode/utf16" "unicode/utf8" // new in golang 1.9 "golang.org/x/sync/syncmap" @@ -966,7 +967,17 @@ func (e *encodeState) string(s string, escapeHTML bool) int { start = i continue } + if start < i { + e.WriteString(s[start:i]) + } + if c < 0x10000 { + e.WriteString(fmt.Sprintf(`\u%04X`, c)) + } else { + r1, r2 := utf16.EncodeRune(c) + e.WriteString(fmt.Sprintf(`\u%04X\u%04X`, r1, r2)) + } i += size + start = i } if start < len(s) { e.WriteString(s[start:]) @@ -1043,7 +1054,17 @@ func (e *encodeState) stringBytes(s []byte, escapeHTML bool) int { start = i continue } + if start < i { + e.Write(s[start:i]) + } + if c < 0x10000 { + e.WriteString(fmt.Sprintf(`\u%04X`, c)) + } else { + r1, r2 := utf16.EncodeRune(c) + e.WriteString(fmt.Sprintf(`\u%04X\u%04X`, r1, r2)) + } i += size + start = i } if start < len(s) { e.Write(s[start:]) diff --git a/encode_test.go b/encode_test.go index 39152b0..9daa736 100644 --- a/encode_test.go +++ b/encode_test.go @@ -231,6 +231,19 @@ func (CText) MarshalText() ([]byte, error) { return []byte(`"<&>"`), nil } + +func TestMarshaler_NeoGo_PR2174(t *testing.T) { + source := "IOU(欠条币):一种支持负数的NEP-17(非严格意义上的)资产,合约无存储区,账户由区块链浏览器统计" + b, err := Marshal(source) + if err != nil { + t.Fatalf("Marshal(c): %v", err) + } + want := `"` + `IOU\uFF08\u6B20\u6761\u5E01\uFF09\uFF1A\u4E00\u79CD\u652F\u6301\u8D1F\u6570\u7684NEP-17\uFF08\u975E\u4E25\u683C\u610F\u4E49\u4E0A\u7684\uFF09\u8D44\u4EA7\uFF0C\u5408\u7EA6\u65E0\u5B58\u50A8\u533A\uFF0C\u8D26\u6237\u7531\u533A\u5757\u94FE\u6D4F\u89C8\u5668\u7EDF\u8BA1` + `"` + if got := string(b); got != want { + t.Errorf("Marshal(c) = %#q, want %#q", got, want) + } +} + func TestMarshalerEscaping(t *testing.T) { var c C want := `"\u003c\u0026\u003e"` diff --git a/stream_test.go b/stream_test.go index d0b3ffb..5be216b 100644 --- a/stream_test.go +++ b/stream_test.go @@ -36,7 +36,7 @@ null true false ["a","b","c"] -{"ß":"long s","K":"Kelvin"} +{"\u00DF":"long s","\u212A":"Kelvin"} 3.14 ` @@ -71,8 +71,8 @@ false >."c" >] { ->."ß": "long s", ->."K": "Kelvin" +>."\u00DF": "long s", +>."\u212A": "Kelvin" >} 3.14 ` From 45541ed3592d5020a154a4b5483d72a18bb3d519 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 10 Jan 2022 21:21:49 +0300 Subject: [PATCH 05/67] tables: escape apostrophe as well for C# compatibility See nspcc-dev/neo-go#2319. --- encode_test.go | 1 + tables.go | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/encode_test.go b/encode_test.go index 9daa736..3b5eab4 100644 --- a/encode_test.go +++ b/encode_test.go @@ -610,6 +610,7 @@ var encodeStringTests = []struct { {"\x1d", `"\u001d"`}, {"\x1e", `"\u001e"`}, {"\x1f", `"\u001f"`}, + {"'", `"\u0027"`}, } func TestEncodeString(t *testing.T) { diff --git a/tables.go b/tables.go index 10acdc1..fc14704 100644 --- a/tables.go +++ b/tables.go @@ -20,7 +20,7 @@ var safeSet = [utf8.RuneSelf]bool{ '$': true, '%': true, '&': true, - '\'': true, + '\'': false, '(': true, ')': true, '*': true, @@ -126,7 +126,7 @@ var htmlSafeSet = [utf8.RuneSelf]bool{ '$': true, '%': true, '&': false, - '\'': true, + '\'': false, '(': true, ')': true, '*': true, From b9bf885034450990d296e7d3b0d61e23323c9067 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 23 Nov 2023 18:20:22 +0300 Subject: [PATCH 06/67] *: escape HTML chars into uppercase unicode bytes Ref. https://github.com/nspcc-dev/neo-go/issues/3224. Signed-off-by: Anna Shaleva --- decode_test.go | 2 +- encode.go | 2 +- encode_test.go | 26 +++++++++++++------------- stream_test.go | 6 +++--- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/decode_test.go b/decode_test.go index 200ab4d..32c8d5b 100644 --- a/decode_test.go +++ b/decode_test.go @@ -1095,7 +1095,7 @@ func TestUnmarshalPtrPtr(t *testing.T) { func TestEscape(t *testing.T) { const input = `"foobar"` + " [\u2028 \u2029]" - const expected = `"\"foobar\"\u003chtml\u003e [\u2028 \u2029]"` + const expected = `"\"foobar\"\u003Chtml\u003E [\u2028 \u2029]"` b, err := Marshal(input) if err != nil { t.Fatalf("Marshal error: %v", err) diff --git a/encode.go b/encode.go index a871f8c..6454b54 100644 --- a/encode.go +++ b/encode.go @@ -268,7 +268,7 @@ func (e *MarshalerError) Error() string { return "json: error calling MarshalJSON for type " + e.Type.String() + ": " + e.Err.Error() } -var hex = "0123456789abcdef" +var hex = "0123456789ABCDEF" // An encodeState encodes JSON into a bytes.Buffer. type encodeState struct { diff --git a/encode_test.go b/encode_test.go index 3b5eab4..9c60253 100644 --- a/encode_test.go +++ b/encode_test.go @@ -246,7 +246,7 @@ func TestMarshaler_NeoGo_PR2174(t *testing.T) { func TestMarshalerEscaping(t *testing.T) { var c C - want := `"\u003c\u0026\u003e"` + want := `"\u003C\u0026\u003E"` b, err := Marshal(c) if err != nil { t.Fatalf("Marshal(c): %v", err) @@ -256,7 +256,7 @@ func TestMarshalerEscaping(t *testing.T) { } var ct CText - want = `"\"\u003c\u0026\u003e\""` + want = `"\"\u003C\u0026\u003E\""` b, err = Marshal(ct) if err != nil { t.Fatalf("Marshal(ct): %v", err) @@ -541,7 +541,7 @@ func TestIssue10281(t *testing.T) { func TestHTMLEscape(t *testing.T) { var b, want bytes.Buffer m := `{"M":"foo &` + "\xe2\x80\xa8 \xe2\x80\xa9" + `"}` - want.Write([]byte(`{"M":"\u003chtml\u003efoo \u0026\u2028 \u2029\u003c/html\u003e"}`)) + want.Write([]byte(`{"M":"\u003Chtml\u003Efoo \u0026\u2028 \u2029\u003C/html\u003E"}`)) HTMLEscape(&b, []byte(m)) if !bytes.Equal(b.Bytes(), want.Bytes()) { t.Errorf("HTMLEscape(&b, []byte(m)) = %s; want %s", b.Bytes(), want.Bytes()) @@ -589,11 +589,11 @@ var encodeStringTests = []struct { {"\x08", `"\u0008"`}, {"\x09", `"\t"`}, {"\x0a", `"\n"`}, - {"\x0b", `"\u000b"`}, - {"\x0c", `"\u000c"`}, + {"\x0b", `"\u000B"`}, + {"\x0c", `"\u000C"`}, {"\x0d", `"\r"`}, - {"\x0e", `"\u000e"`}, - {"\x0f", `"\u000f"`}, + {"\x0e", `"\u000E"`}, + {"\x0f", `"\u000F"`}, {"\x10", `"\u0010"`}, {"\x11", `"\u0011"`}, {"\x12", `"\u0012"`}, @@ -604,12 +604,12 @@ var encodeStringTests = []struct { {"\x17", `"\u0017"`}, {"\x18", `"\u0018"`}, {"\x19", `"\u0019"`}, - {"\x1a", `"\u001a"`}, - {"\x1b", `"\u001b"`}, - {"\x1c", `"\u001c"`}, - {"\x1d", `"\u001d"`}, - {"\x1e", `"\u001e"`}, - {"\x1f", `"\u001f"`}, + {"\x1a", `"\u001A"`}, + {"\x1b", `"\u001B"`}, + {"\x1c", `"\u001C"`}, + {"\x1d", `"\u001D"`}, + {"\x1e", `"\u001E"`}, + {"\x1f", `"\u001F"`}, {"'", `"\u0027"`}, } diff --git a/stream_test.go b/stream_test.go index 5be216b..e99c02b 100644 --- a/stream_test.go +++ b/stream_test.go @@ -99,9 +99,9 @@ func TestEncoderSetEscapeHTML(t *testing.T) { wantEscape string want string }{ - {"c", c, `"\u003c\u0026\u003e"`, `"<&>"`}, - {"ct", ct, `"\"\u003c\u0026\u003e\""`, `"\"<&>\""`}, - {`"<&>"`, "<&>", `"\u003c\u0026\u003e"`, `"<&>"`}, + {"c", c, `"\u003C\u0026\u003E"`, `"<&>"`}, + {"ct", ct, `"\"\u003C\u0026\u003E\""`, `"\"<&>\""`}, + {`"<&>"`, "<&>", `"\u003C\u0026\u003E"`, `"<&>"`}, } { var buf bytes.Buffer enc := NewEncoder(&buf) From cb54cb3dc928eee07a0abc6773a632544e5629e3 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 23 Nov 2023 18:57:39 +0300 Subject: [PATCH 07/67] .github: drop Linter job No linting for this project, sorry. We'd better avoid merge conflicts from the base repository. Signed-off-by: Anna Shaleva --- .github/workflows/run_tests.yml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 39b19e3..281b00e 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -13,17 +13,6 @@ env: GO111MODULE: "on" jobs: - lint: - name: Lint - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: golangci-lint - uses: golangci/golangci-lint-action@v2 - with: - version: latest - test_cover: name: Coverage runs-on: ubuntu-18.04 From c13c5ca75b5d92453098cbe99e636933f02bd429 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 23 Nov 2023 19:00:33 +0300 Subject: [PATCH 08/67] .github: upgrade Go for testing workflows We're interested in the most fresh NeoGo-compatible versions. Signed-off-by: Anna Shaleva --- .github/workflows/run_tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 281b00e..3fcd9bd 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -27,7 +27,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.17 + go-version: 1.21 - name: Restore Go modules from cache uses: actions/cache@v2 @@ -53,7 +53,7 @@ jobs: runs-on: ubuntu-18.04 strategy: matrix: - go_versions: [ '1.15', '1.16' ] + go_versions: [ '1.19', '1.20' ] fail-fast: false steps: - uses: actions/checkout@v2 From 6f8327ad28519c19f2697df29f9e61d31a57732e Mon Sep 17 00:00:00 2001 From: Ekaterina Pavlova Date: Wed, 10 Jan 2024 20:30:18 +0300 Subject: [PATCH 09/67] tables: escape double quotes for C# compatibility Ref nspcc-dev/neo-go#3284. Signed-off-by: Ekaterina Pavlova --- decode_test.go | 2 +- encode.go | 8 ++++---- encode_test.go | 7 ++++--- stream_test.go | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/decode_test.go b/decode_test.go index 32c8d5b..c380fc0 100644 --- a/decode_test.go +++ b/decode_test.go @@ -1095,7 +1095,7 @@ func TestUnmarshalPtrPtr(t *testing.T) { func TestEscape(t *testing.T) { const input = `"foobar"` + " [\u2028 \u2029]" - const expected = `"\"foobar\"\u003Chtml\u003E [\u2028 \u2029]"` + const expected = `"\u0022foobar\u0022\u003Chtml\u003E [\u2028 \u2029]"` b, err := Marshal(input) if err != nil { t.Fatalf("Marshal error: %v", err) diff --git a/encode.go b/encode.go index 6454b54..0427122 100644 --- a/encode.go +++ b/encode.go @@ -914,7 +914,7 @@ func (e *encodeState) string(s string, escapeHTML bool) int { e.WriteString(s[start:i]) } switch b { - case '\\', '"': + case '\\': e.WriteByte('\\') e.WriteByte(b) case '\n': @@ -927,7 +927,7 @@ func (e *encodeState) string(s string, escapeHTML bool) int { e.WriteByte('\\') e.WriteByte('t') default: - // This encodes bytes < 0x20 except for \t, \n and \r. + // This encodes bytes < 0x20 and " except for \t, \n and \r. // If escapeHTML is set, it also escapes <, >, and & // because they can lead to security holes when // user-controlled strings are rendered into JSON @@ -1001,7 +1001,7 @@ func (e *encodeState) stringBytes(s []byte, escapeHTML bool) int { e.Write(s[start:i]) } switch b { - case '\\', '"': + case '\\': e.WriteByte('\\') e.WriteByte(b) case '\n': @@ -1014,7 +1014,7 @@ func (e *encodeState) stringBytes(s []byte, escapeHTML bool) int { e.WriteByte('\\') e.WriteByte('t') default: - // This encodes bytes < 0x20 except for \t, \n and \r. + // This encodes bytes < 0x20 and " except for \t, \n and \r. // If escapeHTML is set, it also escapes <, >, and & // because they can lead to security holes when // user-controlled strings are rendered into JSON diff --git a/encode_test.go b/encode_test.go index 9c60253..7faf9b3 100644 --- a/encode_test.go +++ b/encode_test.go @@ -79,7 +79,7 @@ type StringTag struct { var stringTagExpected = `{ "BoolStr": "true", "IntStr": "42", - "StrStr": "\"xzbit\"" + "StrStr": "\u0022xzbit\u0022" }` func TestStringTag(t *testing.T) { @@ -207,7 +207,7 @@ func TestRefValMarshal(t *testing.T) { V2: 15, V3: new(ValText), } - const want = `{"R0":"ref","R1":"ref","R2":"\"ref\"","R3":"\"ref\"","V0":"val","V1":"val","V2":"\"val\"","V3":"\"val\""}` + const want = `{"R0":"ref","R1":"ref","R2":"\u0022ref\u0022","R3":"\u0022ref\u0022","V0":"val","V1":"val","V2":"\u0022val\u0022","V3":"\u0022val\u0022"}` b, err := Marshal(&s) if err != nil { t.Fatalf("Marshal: %v", err) @@ -256,7 +256,7 @@ func TestMarshalerEscaping(t *testing.T) { } var ct CText - want = `"\"\u003C\u0026\u003E\""` + want = `"\u0022\u003C\u0026\u003E\u0022"` b, err = Marshal(ct) if err != nil { t.Fatalf("Marshal(ct): %v", err) @@ -611,6 +611,7 @@ var encodeStringTests = []struct { {"\x1e", `"\u001E"`}, {"\x1f", `"\u001F"`}, {"'", `"\u0027"`}, + {"\"", `"\u0022"`}, } func TestEncodeString(t *testing.T) { diff --git a/stream_test.go b/stream_test.go index e99c02b..f3b5327 100644 --- a/stream_test.go +++ b/stream_test.go @@ -100,7 +100,7 @@ func TestEncoderSetEscapeHTML(t *testing.T) { want string }{ {"c", c, `"\u003C\u0026\u003E"`, `"<&>"`}, - {"ct", ct, `"\"\u003C\u0026\u003E\""`, `"\"<&>\""`}, + {"ct", ct, `"\u0022\u003C\u0026\u003E\u0022"`, `"\u0022<&>\u0022"`}, {`"<&>"`, "<&>", `"\u003C\u0026\u003E"`, `"<&>"`}, } { var buf bytes.Buffer From a6f15fefeeb5647f6930682ebb8dc6661297e537 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 29 Feb 2024 10:17:30 +0300 Subject: [PATCH 10/67] encode: make it compatible with .NET, fix #3 This makes encoding follow .NET JSON/UTF8 libraries as much as possible, tested with using System; using System.IO; using System.Text.Json; public class Program { public static void Main() { string controls = "\x00\x01"; MemoryStream ms = new MemoryStream(); Utf8JsonWriter writer = new Utf8JsonWriter(ms, new JsonWriterOptions { SkipValidation = true, }); writer.WriteStringValue(controls); writer.Flush(); byte[] res = ms.ToArray(); string result = System.Text.Encoding.UTF8.GetString(res); Console.WriteLine(result); } } which is similar to what Neo does. Can't comment on differences much because that's just the way .NET is, it doesn't use \ufffd, it escapes some controls in a different way, it just does what it wants and we have to follow here to be compatible. Notice that C# `\x` escaping differs from Go significantly if you want to play with it (it's `\xH[H][H][H]` instead of `\xHH`). It also interprets bytes a bit differently. Signed-off-by: Roman Khimov --- decode_test.go | 10 +++++----- encode.go | 20 ++++++++++++++++++-- encode_test.go | 44 ++++++++++---------------------------------- stream_test.go | 4 ++-- tables.go | 14 +++++++------- 5 files changed, 42 insertions(+), 50 deletions(-) diff --git a/decode_test.go b/decode_test.go index c380fc0..5dfbd75 100644 --- a/decode_test.go +++ b/decode_test.go @@ -858,12 +858,12 @@ func TestMarshal(t *testing.T) { var badUTF8 = []struct { in, out string }{ - {"hello\xffworld", `"hello\ufffdworld"`}, + {"hello\xffworld", `"hello\u00FFworld"`}, {"", `""`}, - {"\xff", `"\ufffd"`}, - {"\xff\xff", `"\ufffd\ufffd"`}, - {"a\xffb", `"a\ufffdb"`}, - {"\xe6\x97\xa5\xe6\x9c\xac\xff\xaa\x9e", `"\u65E5\u672C\ufffd\ufffd\ufffd"`}, + {"\xff", `"\u00FF"`}, + {"\xff\xff", `"\u00FF\u00FF"`}, + {"a\xffb", `"a\u00FFb"`}, + {"\xe6\x97\xa5\xe6\x9c\xac\xff\xaa\x9e", `"\u65E5\u672C\u00FF\u00AA\u009E"`}, } func TestMarshalBadUTF8(t *testing.T) { diff --git a/encode.go b/encode.go index 0427122..1e41c84 100644 --- a/encode.go +++ b/encode.go @@ -917,9 +917,15 @@ func (e *encodeState) string(s string, escapeHTML bool) int { case '\\': e.WriteByte('\\') e.WriteByte(b) + case 0x08: + e.WriteByte('\\') + e.WriteByte('b') case '\n': e.WriteByte('\\') e.WriteByte('n') + case 0x0c: + e.WriteByte('\\') + e.WriteByte('f') case '\r': e.WriteByte('\\') e.WriteByte('r') @@ -945,7 +951,9 @@ func (e *encodeState) string(s string, escapeHTML bool) int { if start < i { e.WriteString(s[start:i]) } - e.WriteString(`\ufffd`) + e.WriteString(`\u00`) + e.WriteByte(hex[s[i]>>4]) + e.WriteByte(hex[s[i]&0xF]) i += size start = i continue @@ -1004,9 +1012,15 @@ func (e *encodeState) stringBytes(s []byte, escapeHTML bool) int { case '\\': e.WriteByte('\\') e.WriteByte(b) + case 0x08: + e.WriteByte('\\') + e.WriteByte('b') case '\n': e.WriteByte('\\') e.WriteByte('n') + case 0x0c: + e.WriteByte('\\') + e.WriteByte('f') case '\r': e.WriteByte('\\') e.WriteByte('r') @@ -1032,7 +1046,9 @@ func (e *encodeState) stringBytes(s []byte, escapeHTML bool) int { if start < i { e.Write(s[start:i]) } - e.WriteString(`\ufffd`) + e.WriteString(`\u00`) + e.WriteByte(hex[s[i]>>4]) + e.WriteByte(hex[s[i]&0xF]) i += size start = i continue diff --git a/encode_test.go b/encode_test.go index 7faf9b3..5e0df5b 100644 --- a/encode_test.go +++ b/encode_test.go @@ -578,40 +578,16 @@ var encodeStringTests = []struct { in string out string }{ - {"\x00", `"\u0000"`}, - {"\x01", `"\u0001"`}, - {"\x02", `"\u0002"`}, - {"\x03", `"\u0003"`}, - {"\x04", `"\u0004"`}, - {"\x05", `"\u0005"`}, - {"\x06", `"\u0006"`}, - {"\x07", `"\u0007"`}, - {"\x08", `"\u0008"`}, - {"\x09", `"\t"`}, - {"\x0a", `"\n"`}, - {"\x0b", `"\u000B"`}, - {"\x0c", `"\u000C"`}, - {"\x0d", `"\r"`}, - {"\x0e", `"\u000E"`}, - {"\x0f", `"\u000F"`}, - {"\x10", `"\u0010"`}, - {"\x11", `"\u0011"`}, - {"\x12", `"\u0012"`}, - {"\x13", `"\u0013"`}, - {"\x14", `"\u0014"`}, - {"\x15", `"\u0015"`}, - {"\x16", `"\u0016"`}, - {"\x17", `"\u0017"`}, - {"\x18", `"\u0018"`}, - {"\x19", `"\u0019"`}, - {"\x1a", `"\u001A"`}, - {"\x1b", `"\u001B"`}, - {"\x1c", `"\u001C"`}, - {"\x1d", `"\u001D"`}, - {"\x1e", `"\u001E"`}, - {"\x1f", `"\u001F"`}, - {"'", `"\u0027"`}, - {"\"", `"\u0022"`}, + {"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", `"\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000B\f\r\u000E\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F"`}, + {"\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f", `" !\u0022#$%\u0026\u0027()*\u002B,-./0123456789:;\u003C=\u003E?"`}, + {"\x40\x41\x44\x45\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x54\x55\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f", `"@ADEDEFGHIJKLMNOPQTUTUVWXYZ[\\]^_"`}, + {"\x60\x61\x66\x67\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x76\x77\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f", `"\u0060afgdefghijklmnopqvwtuvwxyz{|}~\u007F"`}, + {"\x80\x81\x88\x89\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x98\x99\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f", `"\u0080\u0081\u0088\u0089\u0084\u0085\u0086\u0087\u0088\u0089\u008A\u008B\u008C\u008D\u008E\u008F\u0090\u0091\u0098\u0099\u0094\u0095\u0096\u0097\u0098\u0099\u009A\u009B\u009C\u009D\u009E\u009F"`}, + {"\xa0\xa1\xaa\xab\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xba\xbb\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf", `"\u00A0\u00A1\u00AA\u00AB\u00A4\u00A5\u00A6\u00A7\u00A8\u00A9\u00AA\u00AB\u00AC\u00AD\u00AE\u00AF\u00B0\u00B1\u00BA\u00BB\u00B4\u00B5\u00B6\u00B7\u00B8\u00B9\u00BA\u00BB\u00BC\u00BD\u00BE\u00BF"`}, + {"\xc0\xc1\xcc\xcd\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xdc\xdd\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf", `"\u00C0\u00C1\u00CC\u00CD\u00C4\u00C5\u00C6\u00C7\u00C8\u00C9\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF\u00D0\u00D1\u00DC\u00DD\u00D4\u00D5\u00D6\u00D7\u00D8\u00D9\u00DA\u00DB\u00DC\u00DD\u00DE\u00DF"`}, + {"\xe0\xe1\xee\xef\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xfe\xff\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff", `"\u00E0\u00E1\u00EE\u00EF\u00E4\u00E5\u00E6\u00E7\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF\u00F0\u00F1\u00FE\u00FF\u00F4\u00F5\u00F6\u00F7\u00F8\u00F9\u00FA\u00FB\u00FC\u00FD\u00FE\u00FF"`}, + {"\xff\xd0", `"\u00FF\u00D0"`}, + {"测试", `"\u6D4B\u8BD5"`}, } func TestEncodeString(t *testing.T) { diff --git a/stream_test.go b/stream_test.go index f3b5327..657448b 100644 --- a/stream_test.go +++ b/stream_test.go @@ -100,8 +100,8 @@ func TestEncoderSetEscapeHTML(t *testing.T) { want string }{ {"c", c, `"\u003C\u0026\u003E"`, `"<&>"`}, - {"ct", ct, `"\u0022\u003C\u0026\u003E\u0022"`, `"\u0022<&>\u0022"`}, - {`"<&>"`, "<&>", `"\u003C\u0026\u003E"`, `"<&>"`}, + {"ct", ct, `"\u0022\u003C\u0026\u003E\u0022"`, `"\u0022<\u0026>\u0022"`}, + {`"<&>"`, "<&>", `"\u003C\u0026\u003E"`, `"<\u0026>"`}, } { var buf bytes.Buffer enc := NewEncoder(&buf) diff --git a/tables.go b/tables.go index fc14704..4ac200d 100644 --- a/tables.go +++ b/tables.go @@ -19,12 +19,12 @@ var safeSet = [utf8.RuneSelf]bool{ '#': true, '$': true, '%': true, - '&': true, + '&': false, '\'': false, '(': true, ')': true, '*': true, - '+': true, + '+': false, ',': true, '-': true, '.': true, @@ -77,7 +77,7 @@ var safeSet = [utf8.RuneSelf]bool{ ']': true, '^': true, '_': true, - '`': true, + '`': false, 'a': true, 'b': true, 'c': true, @@ -108,7 +108,7 @@ var safeSet = [utf8.RuneSelf]bool{ '|': true, '}': true, '~': true, - '\u007f': true, + '\u007f': false, } // htmlSafeSet holds the value true if the ASCII character with the given @@ -130,7 +130,7 @@ var htmlSafeSet = [utf8.RuneSelf]bool{ '(': true, ')': true, '*': true, - '+': true, + '+': false, ',': true, '-': true, '.': true, @@ -183,7 +183,7 @@ var htmlSafeSet = [utf8.RuneSelf]bool{ ']': true, '^': true, '_': true, - '`': true, + '`': false, 'a': true, 'b': true, 'c': true, @@ -214,5 +214,5 @@ var htmlSafeSet = [utf8.RuneSelf]bool{ '|': true, '}': true, '~': true, - '\u007f': true, + '\u007f': false, } From d40136703e2dbff8f6e5a36c0be9827b63080134 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 29 Feb 2024 10:23:27 +0300 Subject: [PATCH 11/67] *: go fmt Signed-off-by: Roman Khimov --- decode.go | 7 +++---- decode_test.go | 12 +++++------ encode.go | 31 ++++++++++++++--------------- encode_test.go | 1 - example_test.go | 4 ++-- fold.go | 5 +++-- stream.go | 53 ++++++++++++++++++++++++------------------------- 7 files changed, 55 insertions(+), 58 deletions(-) diff --git a/decode.go b/decode.go index 6dd5657..dddbd40 100644 --- a/decode.go +++ b/decode.go @@ -85,14 +85,13 @@ import ( // // The JSON null value unmarshals into an interface, map, pointer, or slice // by setting that Go value to nil. Because null is often used in JSON to mean -// ``not present,'' unmarshaling a JSON null into any other Go type has no effect +// “not present,” unmarshaling a JSON null into any other Go type has no effect // on the value and produces no error. // // When unmarshaling quoted strings, invalid UTF-8 or // invalid UTF-16 surrogate pairs are not treated as an error. // Instead, they are replaced by the Unicode replacement // character U+FFFD. -// func Unmarshal(data []byte, v interface{}) error { // Check for well-formedness. // Avoids filling out half a data structure @@ -274,8 +273,8 @@ type decodeState struct { Struct string Field string } - savedError error - useNumber bool + savedError error + useNumber bool useOrderedObject bool } diff --git a/decode_test.go b/decode_test.go index 5dfbd75..1e2430b 100644 --- a/decode_test.go +++ b/decode_test.go @@ -372,12 +372,12 @@ func (b *intWithPtrMarshalText) UnmarshalText(data []byte) error { } type unmarshalTest struct { - in string - ptr interface{} - out interface{} - err error - useNumber bool - golden bool + in string + ptr interface{} + out interface{} + err error + useNumber bool + golden bool useOrderedObject bool } diff --git a/encode.go b/encode.go index 1e41c84..2271798 100644 --- a/encode.go +++ b/encode.go @@ -81,31 +81,31 @@ import ( // // Examples of struct field tags and their meanings: // -// // Field appears in JSON as key "myName". -// Field int `json:"myName"` +// // Field appears in JSON as key "myName". +// Field int `json:"myName"` // -// // Field appears in JSON as key "myName" and -// // the field is omitted from the object if its value is empty, -// // as defined above. -// Field int `json:"myName,omitempty"` +// // Field appears in JSON as key "myName" and +// // the field is omitted from the object if its value is empty, +// // as defined above. +// Field int `json:"myName,omitempty"` // -// // Field appears in JSON as key "Field" (the default), but -// // the field is skipped if empty. -// // Note the leading comma. -// Field int `json:",omitempty"` +// // Field appears in JSON as key "Field" (the default), but +// // the field is skipped if empty. +// // Note the leading comma. +// Field int `json:",omitempty"` // -// // Field is ignored by this package. -// Field int `json:"-"` +// // Field is ignored by this package. +// Field int `json:"-"` // -// // Field appears in JSON as key "-". -// Field int `json:"-,"` +// // Field appears in JSON as key "-". +// Field int `json:"-,"` // // The "string" option signals that a field is stored as JSON inside a // JSON-encoded string. It applies only to fields of string, floating point, // integer, or boolean types. This extra level of encoding is sometimes used // when communicating with JavaScript programs: // -// Int64String int64 `json:",string"` +// Int64String int64 `json:",string"` // // The key name will be used if it's a non-empty string consisting of // only Unicode letters, digits, and ASCII punctuation except quotation @@ -161,7 +161,6 @@ import ( // JSON cannot represent cyclic data structures and Marshal does not // handle them. Passing cyclic structures to Marshal will result in // an infinite recursion. -// func Marshal(v interface{}) ([]byte, error) { e := &encodeState{} err := e.marshal(v, encOpts{escapeHTML: true}) diff --git a/encode_test.go b/encode_test.go index 5e0df5b..d433908 100644 --- a/encode_test.go +++ b/encode_test.go @@ -231,7 +231,6 @@ func (CText) MarshalText() ([]byte, error) { return []byte(`"<&>"`), nil } - func TestMarshaler_NeoGo_PR2174(t *testing.T) { source := "IOU(欠条币):一种支持负数的NEP-17(非严格意义上的)资产,合约无存储区,账户由区块链浏览器统计" b, err := Marshal(source) diff --git a/example_test.go b/example_test.go index 611fe3c..1baf3d8 100644 --- a/example_test.go +++ b/example_test.go @@ -6,8 +6,8 @@ package json_test import ( "bytes" - "github.com/virtuald/go-ordered-json" "fmt" + "github.com/virtuald/go-ordered-json" "io" "log" "os" @@ -312,4 +312,4 @@ func ExampleOrderedObject() { // name=André-Marie Ampère born=1777 died=1836 // Encoded: // {"name":"Hans Christian Ørsted","born":1777,"died":1851,"nationality":"Danish"} -} \ No newline at end of file +} diff --git a/fold.go b/fold.go index 9e17012..ab249b2 100644 --- a/fold.go +++ b/fold.go @@ -24,8 +24,9 @@ const ( // 4) simpleLetterEqualFold, no specials, no non-letters. // // The letters S and K are special because they map to 3 runes, not just 2: -// * S maps to s and to U+017F 'ſ' Latin small letter long s -// * k maps to K and to U+212A 'K' Kelvin sign +// - S maps to s and to U+017F 'ſ' Latin small letter long s +// - k maps to K and to U+212A 'K' Kelvin sign +// // See https://play.golang.org/p/tTxjOc0OGo // // The returned function is specialized for matching against s and diff --git a/stream.go b/stream.go index 744dcd5..041f911 100644 --- a/stream.go +++ b/stream.go @@ -282,41 +282,41 @@ type Member struct { // OrderedObject is used to enable decoding of arbitrary JSON objects while preserving // the order of the keys. Unmarshal and Decoder.Decode are supported. // -// var o OrderedObject -// Unmarshal(json, &o) // decode JSON object, while preserving key order +// var o OrderedObject +// Unmarshal(json, &o) // decode JSON object, while preserving key order // -// var oa []OrderedObject -// Unmarshal(json, &oa) // decode an array of JSON objects, while preserving key order +// var oa []OrderedObject +// Unmarshal(json, &oa) // decode an array of JSON objects, while preserving key order // -// var v interface{} -// d := new Decoder(json) -// d.UseOrderedObject() // decode all JSON objects as OrderedObject rather than map[string]interface{} -// d.Decode(&v) +// var v interface{} +// d := new Decoder(json) +// d.UseOrderedObject() // decode all JSON objects as OrderedObject rather than map[string]interface{} +// d.Decode(&v) // -// type A struct { -// B bool -// Inner OrderedObject -// I int -// } -// var a A -// Unmarshal(&a) // decode A as a JSON object with Inner as a nested object, preserving key order +// type A struct { +// B bool +// Inner OrderedObject +// I int +// } +// var a A +// Unmarshal(&a) // decode A as a JSON object with Inner as a nested object, preserving key order // // OrderedObject can also be used to encode a JSON object in // a specified order. Marshal and Encoder.Encode are supported. // -// var o OrderedObject -// Marshal(o) // encode JSON object, each with keys in OrderedObject order +// var o OrderedObject +// Marshal(o) // encode JSON object, each with keys in OrderedObject order // -// var oa []OrderedObject -// Marshal(oa) // encode an array of JSON objects, with keys in OrderedObject order +// var oa []OrderedObject +// Marshal(oa) // encode an array of JSON objects, with keys in OrderedObject order // -// type A struct { -// B bool -// Inner OrderedObject -// I int -// } -// var a A = createA() -// Marshal(a) // encode A as a JSON object with Inner as a nested object +// type A struct { +// B bool +// Inner OrderedObject +// I int +// } +// var a A = createA() +// Marshal(a) // encode A as a JSON object with Inner as a nested object type OrderedObject []Member // A Token holds a value of one of these types: @@ -327,7 +327,6 @@ type OrderedObject []Member // Number, for JSON numbers // string, for JSON string literals // nil, for JSON null -// type Token interface{} const ( From bbb02e84c2cf9f215da527e71815d21b7fbff230 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 29 Feb 2024 10:31:17 +0300 Subject: [PATCH 12/67] README: make it a bit more useful Signed-off-by: Roman Khimov --- README.md | 41 ++++++++++++----------------------------- 1 file changed, 12 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index ddf2c3d..77b2460 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,18 @@ -nspcc-dev fork of go-ordered-json -=============== +.NET-compatible JSON library +============================ -There are some legacy/stupid applications[1] that you need to interoperate with, -and they for whatever reason require that the JSON you're using is ordered in -a particular way (contrary to the JSON specifications). +It's made for 100% compatibility with the JSON variation used by +[Neo blockchain](https://github.com/neo-project/). There are three problems +there: + * it's ordered (that's why it's a fork of [go-ordered-json](https://github.com/virtuald/go-ordered-json)) + * it has different conventions regarding control and "special" symbols + * it has different conventions wrt incorrect UTF-8 -Unfortunately, the golang authors are not willing to support such a broken use -case, so on [their advice](https://groups.google.com/forum/#!topic/golang-dev/zBQwhm3VfvU) -this is a fork of the golang encoding/json package, with the ordered JSON -support originating with a patch from -[Peter Waldschmidt](https://go-review.googlesource.com/c/7930/). +The primary user of this library is [NeoGo](https://github.com/nspcc-dev/neo-go/), +it has to be 100% compatible with C# implementation to correctly process +transactions, that's why we're maintaining this library and solving any +inconsistencies with .NET libraries if found. **If you can, you should avoid using this package**. However, if you can't avoid it, then you are welcome to. Provided under the MIT license, just like golang. - -Known broken applications -------------------------- - -* [1][Windows Communication Foundation Json __type ordering](https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/stand-alone-json-serialization#type-hint-position-in-json-objects) -* [2][NEO node](https://github.com/neo-project/neo/tree/master/src/neo/IO/Json) - -Fork motivations -------------------------- - -NEO project has its own implementation of JSON serializer which is more a JSON -dialect rather than standard-compatible implementation. Until JSON serialisation -format affects contract states we need to be byte-to-byte compatible with the -reference JSON serializer. This fork contains the following compatibility quirks: - -* JSON serializer is ordered (see https://github.com/nspcc-dev/neo-go/pull/2026) (implemented in the original [virtuald/go-ordered-json](https://github.com/virtuald/go-ordered-json) repository) -* JSON serializer escapes non-ascii characters while marshalling (see https://github.com/nspcc-dev/neo-go/pull/2174) - -More compatibility quirks may be added in the future. \ No newline at end of file From a2ece043b3673c54698f2a069b75fae8d760c639 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 29 Feb 2024 10:39:02 +0300 Subject: [PATCH 13/67] workflows: update OS/action versions Seems like they're so old they can't be executed. Drop useless actions at the same time. Signed-off-by: Roman Khimov --- .github/workflows/run_tests.yml | 34 ++++++++------------------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 3fcd9bd..1dc7266 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -15,34 +15,25 @@ env: jobs: test_cover: name: Coverage - runs-on: ubuntu-18.04 + runs-on: ubuntu-22.04 env: CGO_ENABLED: 0 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v5 with: go-version: 1.21 - - name: Restore Go modules from cache - uses: actions/cache@v2 - with: - path: /home/runner/go/pkg/mod - key: deps-${{ hashFiles('go.sum') }} - - - name: Update Go modules - run: go mod download -json - - name: Write coverage profile run: go test -v ./... -coverprofile=./coverage.txt -covermode=atomic -coverpkg=./pkg...,./cli/... - name: Upload coverage results to Codecov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v2 with: fail_ci_if_error: false path_to_write_report: ./coverage.txt @@ -50,29 +41,20 @@ jobs: tests: name: Go - runs-on: ubuntu-18.04 + runs-on: ubuntu-22.04 strategy: matrix: go_versions: [ '1.19', '1.20' ] fail-fast: false steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v5 with: go-version: '${{ matrix.go_versions }}' - - name: Restore Go modules from cache - uses: actions/cache@v2 - with: - path: /home/runner/go/pkg/mod - key: deps-${{ hashFiles('go.sum') }} - - - name: Update Go modules - run: go mod download -json - - name: Run tests - run: go test -v -race ./... \ No newline at end of file + run: go test -v -race ./... From 13d6bbe0df854eb65e8f6494285e9da5b76c55ed Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 29 Feb 2024 15:09:48 +0300 Subject: [PATCH 14/67] *: goimports Signed-off-by: Roman Khimov --- encode.go | 1 + example_test.go | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/encode.go b/encode.go index 2271798..4881854 100644 --- a/encode.go +++ b/encode.go @@ -26,6 +26,7 @@ import ( "unicode" "unicode/utf16" "unicode/utf8" + // new in golang 1.9 "golang.org/x/sync/syncmap" ) diff --git a/example_test.go b/example_test.go index 1baf3d8..9bc4899 100644 --- a/example_test.go +++ b/example_test.go @@ -7,11 +7,12 @@ package json_test import ( "bytes" "fmt" - "github.com/virtuald/go-ordered-json" "io" "log" "os" "strings" + + json "github.com/virtuald/go-ordered-json" ) func ExampleMarshal() { From a7eb203c5085c7dc2a0add68b6dafc3d44b304de Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 29 Feb 2024 15:16:48 +0300 Subject: [PATCH 15/67] workflows: use newer Go Signed-off-by: Roman Khimov --- .github/workflows/run_tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 1dc7266..6799a31 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -27,7 +27,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: 1.21 + go-version: 1.22 - name: Write coverage profile run: go test -v ./... -coverprofile=./coverage.txt -covermode=atomic -coverpkg=./pkg...,./cli/... @@ -44,7 +44,7 @@ jobs: runs-on: ubuntu-22.04 strategy: matrix: - go_versions: [ '1.19', '1.20' ] + go_versions: [ '1.20', '1.21' ] fail-fast: false steps: - uses: actions/checkout@v4 From f2fd72d06ee72db80c23948928099c8c97b7190e Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 29 Feb 2024 15:18:43 +0300 Subject: [PATCH 16/67] workflows: test on windows and amd64/arm64 macs Signed-off-by: Roman Khimov --- .github/workflows/run_tests.yml | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 6799a31..0377f8e 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -41,10 +41,28 @@ jobs: tests: name: Go - runs-on: ubuntu-22.04 + runs-on: ${{ matrix.os }} strategy: matrix: - go_versions: [ '1.20', '1.21' ] + go_versions: [ '1.20', '1.21', '1.22' ] + os: [ubuntu-22.04, windows-2022, macos-12, macos-14] + exclude: + # Only latest Go version for Windows and MacOS. + - os: windows-2022 + go_versions: '1.20' + - os: windows-2022 + go_versions: '1.21' + - os: macos-12 + go_versions: '1.20' + - os: macos-12 + go_versions: '1.21' + - os: macos-14 + go_versions: '1.20' + - os: macos-14 + go_versions: '1.21' + # Exclude latest Go version for Ubuntu as Coverage uses it. + - os: ubuntu-20.04 + go_versions: '1.22' fail-fast: false steps: - uses: actions/checkout@v4 From 811dd20bcb090f8313bc1b9296128fa76a103754 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 29 Feb 2024 15:19:04 +0300 Subject: [PATCH 17/67] go.mod: use Go 1.20 min, fix #5 Signed-off-by: Roman Khimov --- go.mod | 2 +- go.sum | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 49c47f8..fbb0b33 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/nspcc-dev/go-ordered-json -go 1.15 +go 1.20 require ( github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 diff --git a/go.sum b/go.sum index 7b021a4..93be5ec 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,4 @@ github.com/nspcc-dev/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 h1:nNfKXQszRbLcbMVe0t/GeiXxw1ouQcJzzxLlDzB7SvE= github.com/nspcc-dev/go-ordered-json v0.0.0-20170621173500-b18e6e673d74/go.mod h1:IL5YXjbcudZiFB5Xi27bMlPRWzsm0LkIpszR+PFqTnU= -github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 h1:JwtAtbp7r/7QSyGz8mKUbYJBg2+6Cd7OjM8o/GNOcVo= -github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74/go.mod h1:RmMWU37GKR2s6pgrIEB4ixgpVCt/cf7dnJv3fuH1J1c= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From bcb1c4b21df7e72dd8ce3a3a5b7cd3b87f3eb4f7 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 29 Feb 2024 15:22:20 +0300 Subject: [PATCH 18/67] encode: use built-in sync.Map type It's available since Go 1.9. Signed-off-by: Roman Khimov --- encode.go | 5 +---- go.mod | 7 +++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/encode.go b/encode.go index 4881854..37fd0f1 100644 --- a/encode.go +++ b/encode.go @@ -26,9 +26,6 @@ import ( "unicode" "unicode/utf16" "unicode/utf8" - - // new in golang 1.9 - "golang.org/x/sync/syncmap" ) // Marshal returns the JSON encoding of v. @@ -338,7 +335,7 @@ type encOpts struct { type encoderFunc func(e *encodeState, v reflect.Value, opts encOpts) -var encoderCache syncmap.Map // map[reflect.Type]encoderFunc +var encoderCache sync.Map // map[reflect.Type]encoderFunc func valueEncoder(v reflect.Value) encoderFunc { if !v.IsValid() { diff --git a/go.mod b/go.mod index fbb0b33..4e72d7f 100644 --- a/go.mod +++ b/go.mod @@ -2,9 +2,8 @@ module github.com/nspcc-dev/go-ordered-json go 1.20 -require ( - github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c -) +require github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 + +require golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect replace github.com/virtuald/go-ordered-json => github.com/nspcc-dev/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 From 3da2262db467ab0c9678fcb97f3a9285edcca09b Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 29 Feb 2024 15:27:17 +0300 Subject: [PATCH 19/67] example: simplify dependencies We don't need virtuald variant here. Signed-off-by: Roman Khimov --- example_test.go | 4 ++-- go.mod | 5 ----- go.sum | 4 ---- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/example_test.go b/example_test.go index 9bc4899..d67c449 100644 --- a/example_test.go +++ b/example_test.go @@ -12,7 +12,7 @@ import ( "os" "strings" - json "github.com/virtuald/go-ordered-json" + json "github.com/nspcc-dev/go-ordered-json" ) func ExampleMarshal() { @@ -312,5 +312,5 @@ func ExampleOrderedObject() { // name=Issac Newton born=1643 died=1727 // name=André-Marie Ampère born=1777 died=1836 // Encoded: - // {"name":"Hans Christian Ørsted","born":1777,"died":1851,"nationality":"Danish"} + // {"name":"Hans Christian \u00D8rsted","born":1777,"died":1851,"nationality":"Danish"} } diff --git a/go.mod b/go.mod index 4e72d7f..3bc08d1 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,3 @@ module github.com/nspcc-dev/go-ordered-json go 1.20 -require github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 - -require golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - -replace github.com/virtuald/go-ordered-json => github.com/nspcc-dev/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 diff --git a/go.sum b/go.sum index 93be5ec..e69de29 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +0,0 @@ -github.com/nspcc-dev/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 h1:nNfKXQszRbLcbMVe0t/GeiXxw1ouQcJzzxLlDzB7SvE= -github.com/nspcc-dev/go-ordered-json v0.0.0-20170621173500-b18e6e673d74/go.mod h1:IL5YXjbcudZiFB5Xi27bMlPRWzsm0LkIpszR+PFqTnU= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From 2d9e480916dd7e80852c993d180e6acedbd88307 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 29 Feb 2024 15:29:03 +0300 Subject: [PATCH 20/67] tests: stop using deprecated (since Go 1.16) io/ioutil Signed-off-by: Roman Khimov --- bench_test.go | 6 +++--- stream_test.go | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/bench_test.go b/bench_test.go index 85d7ae0..6844185 100644 --- a/bench_test.go +++ b/bench_test.go @@ -13,7 +13,7 @@ package json import ( "bytes" "compress/gzip" - "io/ioutil" + "io" "os" "strings" "testing" @@ -47,7 +47,7 @@ func codeInit() { if err != nil { panic(err) } - data, err := ioutil.ReadAll(gz) + data, err := io.ReadAll(gz) if err != nil { panic(err) } @@ -83,7 +83,7 @@ func BenchmarkCodeEncoder(b *testing.B) { b.StartTimer() } b.RunParallel(func(pb *testing.PB) { - enc := NewEncoder(ioutil.Discard) + enc := NewEncoder(io.Discard) for pb.Next() { if err := enc.Encode(&codeStruct); err != nil { b.Fatal("Encode:", err) diff --git a/stream_test.go b/stream_test.go index 657448b..440473a 100644 --- a/stream_test.go +++ b/stream_test.go @@ -7,7 +7,6 @@ package json import ( "bytes" "io" - "io/ioutil" "log" "net" "net/http" @@ -168,7 +167,7 @@ func TestDecoderBuffered(t *testing.T) { if m.Name != "Gopher" { t.Errorf("Name = %q; want Gopher", m.Name) } - rest, err := ioutil.ReadAll(d.Buffered()) + rest, err := io.ReadAll(d.Buffered()) if err != nil { t.Fatal(err) } @@ -270,7 +269,7 @@ func BenchmarkEncoderEncode(b *testing.B) { v := &T{"foo", "bar"} b.RunParallel(func(pb *testing.PB) { for pb.Next() { - if err := NewEncoder(ioutil.Discard).Encode(v); err != nil { + if err := NewEncoder(io.Discard).Encode(v); err != nil { b.Fatal(err) } } From 195264001e8cefa8cc46b2dd0760d6a93948bdb5 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 29 Feb 2024 15:32:26 +0300 Subject: [PATCH 21/67] *: use errors.Is for error checks Signed-off-by: Roman Khimov --- example_test.go | 5 +++-- stream.go | 2 +- stream_test.go | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/example_test.go b/example_test.go index d67c449..62817df 100644 --- a/example_test.go +++ b/example_test.go @@ -6,6 +6,7 @@ package json_test import ( "bytes" + "errors" "fmt" "io" "log" @@ -69,7 +70,7 @@ func ExampleDecoder() { dec := json.NewDecoder(strings.NewReader(jsonStream)) for { var m Message - if err := dec.Decode(&m); err == io.EOF { + if err := dec.Decode(&m); errors.Is(err, io.EOF) { break } else if err != nil { log.Fatal(err) @@ -92,7 +93,7 @@ func ExampleDecoder_Token() { dec := json.NewDecoder(strings.NewReader(jsonStream)) for { t, err := dec.Token() - if err == io.EOF { + if errors.Is(err, io.EOF) { break } if err != nil { diff --git a/stream.go b/stream.go index 041f911..44a92d1 100644 --- a/stream.go +++ b/stream.go @@ -116,7 +116,7 @@ Input: // Did the last read have an error? // Delayed until now to allow buffer scan. if err != nil { - if err == io.EOF { + if errors.Is(err, io.EOF) { if dec.scan.step(&dec.scan, ' ') == scanEnd { break Input } diff --git a/stream_test.go b/stream_test.go index 440473a..2a71bee 100644 --- a/stream_test.go +++ b/stream_test.go @@ -6,6 +6,7 @@ package json import ( "bytes" + "errors" "io" "log" "net" @@ -370,7 +371,7 @@ func TestDecodeInStream(t *testing.T) { t.Errorf("case %v: Expected error %v in %q, but was %v", ci, experr, tcase.json, err) } break - } else if err == io.EOF { + } else if errors.Is(err, io.EOF) { t.Errorf("case %v: Unexpected EOF in %q", ci, tcase.json) break } else if err != nil { From 58d6d50c9af2f1eae34e3103c98ea67260c753dc Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 29 Feb 2024 15:35:21 +0300 Subject: [PATCH 22/67] *: interface{} -> any Hi, Go 1.18. Signed-off-by: Roman Khimov --- bench_test.go | 2 +- decode.go | 32 ++++++------- decode_test.go | 122 ++++++++++++++++++++++++------------------------ encode.go | 6 +-- encode_test.go | 78 +++++++++++++++---------------- example_test.go | 2 +- scanner_test.go | 10 ++-- stream.go | 20 ++++---- stream_test.go | 66 +++++++++++++------------- tagkey_test.go | 6 +-- 10 files changed, 172 insertions(+), 172 deletions(-) diff --git a/bench_test.go b/bench_test.go index 6844185..f5a3613 100644 --- a/bench_test.go +++ b/bench_test.go @@ -138,7 +138,7 @@ func BenchmarkDecoderStream(b *testing.B) { var buf bytes.Buffer dec := NewDecoder(&buf) buf.WriteString(`"` + strings.Repeat("x", 1000000) + `"` + "\n\n\n") - var x interface{} + var x any if err := dec.Decode(&x); err != nil { b.Fatal("Decode:", err) } diff --git a/decode.go b/decode.go index dddbd40..bbcd2b8 100644 --- a/decode.go +++ b/decode.go @@ -53,8 +53,8 @@ import ( // bool, for JSON booleans // float64, for JSON numbers // string, for JSON strings -// []interface{}, for JSON arrays -// map[string]interface{}, for JSON objects +// []any, for JSON arrays +// map[string]any, for JSON objects // nil for JSON null // // To unmarshal a JSON array into a slice, Unmarshal resets the slice length @@ -92,7 +92,7 @@ import ( // invalid UTF-16 surrogate pairs are not treated as an error. // Instead, they are replaced by the Unicode replacement // character U+FFFD. -func Unmarshal(data []byte, v interface{}) error { +func Unmarshal(data []byte, v any) error { // Check for well-formedness. // Avoids filling out half a data structure // before discovering a JSON syntax error. @@ -165,7 +165,7 @@ func (e *InvalidUnmarshalError) Error() string { return "json: Unmarshal(nil " + e.Type.String() + ")" } -func (d *decodeState) unmarshal(v interface{}) (err error) { +func (d *decodeState) unmarshal(v any) (err error) { defer func() { if r := recover(); r != nil { if _, ok := r.(runtime.Error); ok { @@ -374,7 +374,7 @@ func (d *decodeState) scanWhile(op int) int { // by different goroutines unmarshalling skipped fields. var ( discardObject = reflect.ValueOf(struct{}{}) - discardArray = reflect.ValueOf([0]interface{}{}) + discardArray = reflect.ValueOf([0]any{}) ) // value decodes a JSON value from d.data[d.off:] into the value. @@ -408,7 +408,7 @@ type unquotedValue struct{} // quoted string literal or literal null into an interface value. // If it finds anything other than a quoted string literal or null, // valueQuoted returns unquotedValue{}. -func (d *decodeState) valueQuoted() interface{} { +func (d *decodeState) valueQuoted() any { switch op := d.scanWhile(scanSkipSpace); op { default: d.error(errPhase) @@ -802,7 +802,7 @@ func (d *decodeState) literal(v reflect.Value) { // convertNumber converts the number literal s to a float64 or a Number // depending on the setting of d.useNumber. -func (d *decodeState) convertNumber(s string) (interface{}, error) { +func (d *decodeState) convertNumber(s string) (any, error) { if d.useNumber { return Number(s), nil } @@ -1009,8 +1009,8 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool // in an empty interface. They are not strictly necessary, // but they avoid the weight of reflection in this common case. -// valueInterface is like value but returns interface{} -func (d *decodeState) valueInterface() interface{} { +// valueInterface is like value but returns any +func (d *decodeState) valueInterface() any { switch d.scanWhile(scanSkipSpace) { default: d.error(errPhase) @@ -1024,9 +1024,9 @@ func (d *decodeState) valueInterface() interface{} { } } -// arrayInterface is like array but returns []interface{}. -func (d *decodeState) arrayInterface() []interface{} { - var v = make([]interface{}, 0) +// arrayInterface is like array but returns []any. +func (d *decodeState) arrayInterface() []any { + var v = make([]any, 0) for { // Look ahead for ] - can only happen on first iteration. op := d.scanWhile(scanSkipSpace) @@ -1052,9 +1052,9 @@ func (d *decodeState) arrayInterface() []interface{} { return v } -// objectInterface is like object but returns map[string]interface{} or []OrderedObject. -func (d *decodeState) objectInterface(forceOrderedObject bool) interface{} { - m := make(map[string]interface{}) +// objectInterface is like object but returns map[string]any or []OrderedObject. +func (d *decodeState) objectInterface(forceOrderedObject bool) any { + m := make(map[string]any) v := make(OrderedObject, 0) for { // Read opening " of string key or closing }. @@ -1109,7 +1109,7 @@ func (d *decodeState) objectInterface(forceOrderedObject bool) interface{} { } // literalInterface is like literal but returns an interface value. -func (d *decodeState) literalInterface() interface{} { +func (d *decodeState) literalInterface() any { // All bytes inside literal return scanContinue op code. start := d.off - 1 op := d.scanWhile(scanContinue) diff --git a/decode_test.go b/decode_test.go index 1e2430b..92a4b5f 100644 --- a/decode_test.go +++ b/decode_test.go @@ -31,7 +31,7 @@ type U struct { } type V struct { - F1 interface{} + F1 any F2 int32 F3 Number F4 *VOuter @@ -43,18 +43,18 @@ type VOuter struct { // ifaceNumAsFloat64/ifaceNumAsNumber are used to test unmarshaling with and // without UseNumber -var ifaceNumAsFloat64 = map[string]interface{}{ +var ifaceNumAsFloat64 = map[string]any{ "k1": float64(1), "k2": "s", - "k3": []interface{}{float64(1), float64(2.0), float64(3e-3)}, - "k4": map[string]interface{}{"kk1": "s", "kk2": float64(2)}, + "k3": []any{float64(1), float64(2.0), float64(3e-3)}, + "k4": map[string]any{"kk1": "s", "kk2": float64(2)}, } -var ifaceNumAsNumber = map[string]interface{}{ +var ifaceNumAsNumber = map[string]any{ "k1": Number("1"), "k2": "s", - "k3": []interface{}{Number("1"), Number("2.0"), Number("3e-3")}, - "k4": map[string]interface{}{"kk1": "s", "kk2": Number("2")}, + "k3": []any{Number("1"), Number("2.0"), Number("3e-3")}, + "k4": map[string]any{"kk1": "s", "kk2": Number("2")}, } type tx struct { @@ -251,9 +251,9 @@ type Ambig struct { } type XYZ struct { - X interface{} - Y interface{} - Z interface{} + X any + Y any + Z any } func sliceAddr(x []int) *[]int { return &x } @@ -373,8 +373,8 @@ func (b *intWithPtrMarshalText) UnmarshalText(data []byte) error { type unmarshalTest struct { in string - ptr interface{} - out interface{} + ptr any + out any err error useNumber bool golden bool @@ -393,19 +393,19 @@ var unmarshalTests = []unmarshalTest{ {in: `-5`, ptr: new(int16), out: int16(-5)}, {in: `2`, ptr: new(Number), out: Number("2"), useNumber: true}, {in: `2`, ptr: new(Number), out: Number("2")}, - {in: `2`, ptr: new(interface{}), out: float64(2.0)}, - {in: `2`, ptr: new(interface{}), out: Number("2"), useNumber: true}, + {in: `2`, ptr: new(any), out: float64(2.0)}, + {in: `2`, ptr: new(any), out: Number("2"), useNumber: true}, {in: `"a\u1234"`, ptr: new(string), out: "a\u1234"}, {in: `"http:\/\/"`, ptr: new(string), out: "http://"}, {in: `"g-clef: \uD834\uDD1E"`, ptr: new(string), out: "g-clef: \U0001D11E"}, {in: `"invalid: \uD834x\uDD1E"`, ptr: new(string), out: "invalid: \uFFFDx\uFFFD"}, - {in: "null", ptr: new(interface{}), out: nil}, + {in: "null", ptr: new(any), out: nil}, {in: `{"X": [1,2,3], "Y": 4}`, ptr: new(T), out: T{Y: 4}, err: &UnmarshalTypeError{"array", reflect.TypeOf(""), 7, "T", "X"}}, {in: `{"x": 1}`, ptr: new(tx), out: tx{}}, {in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: float64(1), F2: int32(2), F3: Number("3")}}, {in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: Number("1"), F2: int32(2), F3: Number("3")}, useNumber: true}, - {in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(interface{}), out: ifaceNumAsFloat64}, - {in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(interface{}), out: ifaceNumAsNumber, useNumber: true}, + {in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(any), out: ifaceNumAsFloat64}, + {in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(any), out: ifaceNumAsNumber, useNumber: true}, // raw values with whitespace {in: "\n true ", ptr: new(bool), out: true}, @@ -442,10 +442,10 @@ var unmarshalTests = []unmarshalTest{ {in: `[1, 2, 3]`, ptr: new([5]int), out: [5]int{1, 2, 3, 0, 0}}, // empty array to interface test - {in: `[]`, ptr: new([]interface{}), out: []interface{}{}}, - {in: `null`, ptr: new([]interface{}), out: []interface{}(nil)}, - {in: `{"T":[]}`, ptr: new(map[string]interface{}), out: map[string]interface{}{"T": []interface{}{}}}, - {in: `{"T":null}`, ptr: new(map[string]interface{}), out: map[string]interface{}{"T": interface{}(nil)}}, + {in: `[]`, ptr: new([]any), out: []any{}}, + {in: `null`, ptr: new([]any), out: []any(nil)}, + {in: `{"T":[]}`, ptr: new(map[string]any), out: map[string]any{"T": []any{}}}, + {in: `{"T":null}`, ptr: new(map[string]any), out: map[string]any{"T": any(nil)}}, // composite tests {in: allValueIndent, ptr: new(All), out: allValue}, @@ -795,27 +795,27 @@ var unmarshalTests = []unmarshalTest{ {in: `{"B": "null"}`, ptr: new(B), out: B{false}}, {in: `{"B": "nul"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "nul" into bool`)}, - // OrderedObject tests - into interface{} - {in: `{"a":"1","b":2,"c":3}`, ptr: new(interface{}), useOrderedObject: true, + // OrderedObject tests - into any + {in: `{"a":"1","b":2,"c":3}`, ptr: new(any), useOrderedObject: true, out: OrderedObject{{"a", "1"}, {"b", float64(2)}, {"c", float64(3)}}}, - {in: `[{"a":"1","b":2}]`, ptr: new(interface{}), useOrderedObject: true, - out: []interface{}{OrderedObject{{"a", "1"}, {"b", float64(2)}}}}, - {in: `{"a":null,"b": {"c":true} }`, ptr: new(interface{}), useOrderedObject: true, + {in: `[{"a":"1","b":2}]`, ptr: new(any), useOrderedObject: true, + out: []any{OrderedObject{{"a", "1"}, {"b", float64(2)}}}}, + {in: `{"a":null,"b": {"c":true} }`, ptr: new(any), useOrderedObject: true, out: OrderedObject{{"a", nil}, {"b", OrderedObject{{"c", true}}}}}, - {in: `{"T":[]}`, ptr: new(interface{}), useOrderedObject: true, - out: OrderedObject{{Key: "T", Value: []interface{}{}}}}, - {in: `{"T":null}`, ptr: new(interface{}), useOrderedObject: true, + {in: `{"T":[]}`, ptr: new(any), useOrderedObject: true, + out: OrderedObject{{Key: "T", Value: []any{}}}}, + {in: `{"T":null}`, ptr: new(any), useOrderedObject: true, out: OrderedObject{{Key: "T", Value: nil}}}, - // OrderedObject tests - into map[string]interface{} - {in: `{"a":"1","b":2,"c":3}`, ptr: new(map[string]interface{}), useOrderedObject: true, - out: map[string]interface{}{"a": "1", "b": float64(2), "c": float64(3)}}, - {in: `{"a":null,"b": {"c":true} }`, ptr: new(map[string]interface{}), useOrderedObject: true, - out: map[string]interface{}{"a": nil, "b": OrderedObject{{"c", true}}}}, - {in: `{"T":[]}`, ptr: new(map[string]interface{}), useOrderedObject: true, - out: map[string]interface{}{"T": []interface{}{}}}, - {in: `{"T":null}`, ptr: new(map[string]interface{}), useOrderedObject: true, - out: map[string]interface{}{"T": nil}}, + // OrderedObject tests - into map[string]any + {in: `{"a":"1","b":2,"c":3}`, ptr: new(map[string]any), useOrderedObject: true, + out: map[string]any{"a": "1", "b": float64(2), "c": float64(3)}}, + {in: `{"a":null,"b": {"c":true} }`, ptr: new(map[string]any), useOrderedObject: true, + out: map[string]any{"a": nil, "b": OrderedObject{{"c", true}}}}, + {in: `{"T":[]}`, ptr: new(map[string]any), useOrderedObject: true, + out: map[string]any{"T": []any{}}}, + {in: `{"T":null}`, ptr: new(map[string]any), useOrderedObject: true, + out: map[string]any{"T": nil}}, // OrderedObject tests - into OrderedObject {in: `{"a":"1","b":2,"c":3}`, ptr: new(OrderedObject), useOrderedObject: true, @@ -826,7 +826,7 @@ var unmarshalTests = []unmarshalTest{ {in: `{"a":null,"b": {"c":true} }`, ptr: new(OrderedObject), useOrderedObject: true, out: OrderedObject{{"a", nil}, {"b", OrderedObject{{"c", true}}}}}, {in: `{"a":null,"b": {"c":true} }`, ptr: new(OrderedObject), - out: OrderedObject{{"a", nil}, {"b", map[string]interface{}{"c": true}}}}, + out: OrderedObject{{"a", nil}, {"b", map[string]any{"c": true}}}}, // OrderedObject tests -into []OrderedObject {in: `[{"a":"1","b":2},{"c":3}]`, ptr: &[]OrderedObject{}, @@ -1001,7 +1001,7 @@ func TestUnmarshal(t *testing.T) { func TestUnmarshalMarshal(t *testing.T) { initBig() - var v interface{} + var v any if err := Unmarshal(jsonBig, &v); err != nil { t.Fatalf("Unmarshal: %v", err) } @@ -1073,7 +1073,7 @@ type Xint struct { func TestUnmarshalInterface(t *testing.T) { var xint Xint - var i interface{} = &xint + var i any = &xint if err := Unmarshal([]byte(`{"X":1}`), &i); err != nil { t.Fatalf("Unmarshal: %v", err) } @@ -1204,8 +1204,8 @@ type All struct { PSmall *Small PPSmall **Small - Interface interface{} - PInterface *interface{} + Interface any + PInterface *any unexported int } @@ -1536,9 +1536,9 @@ func intpp(x *int) **int { } var interfaceSetTests = []struct { - pre interface{} + pre any json string - post interface{} + post any }{ {"foo", `"bar"`, "bar"}, {"foo", `2`, 2.0}, @@ -1557,7 +1557,7 @@ var interfaceSetTests = []struct { func TestInterfaceSet(t *testing.T) { for _, tt := range interfaceSetTests { - b := struct{ X interface{} }{tt.pre} + b := struct{ X any }{tt.pre} blob := `{"X":` + tt.json + `}` if err := Unmarshal([]byte(blob), &b); err != nil { t.Errorf("Unmarshal %#q: %v", blob, err) @@ -1587,7 +1587,7 @@ type NullTest struct { PBool *bool Map map[string]string Slice []string - Interface interface{} + Interface any PRaw *RawMessage PTime *time.Time @@ -1622,7 +1622,7 @@ type NullTestStrings struct { PBool *bool `json:",string"` Map map[string]string `json:",string"` Slice []string `json:",string"` - Interface interface{} `json:",string"` + Interface any `json:",string"` PRaw *RawMessage `json:",string"` PTime *time.Time `json:",string"` @@ -1843,7 +1843,7 @@ func TestSliceOfCustomByte(t *testing.T) { } var decodeTypeErrorTests = []struct { - dest interface{} + dest any src string }{ {new(string), `{"user": "name"}`}, // issue 4628. @@ -1876,7 +1876,7 @@ var unmarshalSyntaxTests = []string{ } func TestUnmarshalSyntax(t *testing.T) { - var x interface{} + var x any for _, src := range unmarshalSyntaxTests { err := Unmarshal([]byte(src), &x) if _, ok := err.(*SyntaxError); !ok { @@ -1889,8 +1889,8 @@ func TestUnmarshalSyntax(t *testing.T) { // Issue 4660 type unexportedFields struct { Name string - m map[string]interface{} `json:"-"` - m2 map[string]interface{} `json:"abcd"` + m map[string]any `json:"-"` + m2 map[string]any `json:"abcd"` } func TestUnmarshalUnexported(t *testing.T) { @@ -1939,7 +1939,7 @@ func TestUnmarshalJSONLiteralError(t *testing.T) { // Issue 3717 func TestSkipArrayObjects(t *testing.T) { json := `[{}]` - var dest [0]interface{} + var dest [0]any err := Unmarshal([]byte(json), &dest) if err != nil { @@ -1950,13 +1950,13 @@ func TestSkipArrayObjects(t *testing.T) { // Test semantics of pre-filled struct fields and pre-filled map fields. // Issue 4900. func TestPrefilled(t *testing.T) { - ptrToMap := func(m map[string]interface{}) *map[string]interface{} { return &m } + ptrToMap := func(m map[string]any) *map[string]any { return &m } // Values here change, cannot reuse table across runs. var prefillTests = []struct { in string - ptr interface{} - out interface{} + ptr any + out any }{ { in: `{"X": 1, "Y": 2}`, @@ -1965,8 +1965,8 @@ func TestPrefilled(t *testing.T) { }, { in: `{"X": 1, "Y": 2}`, - ptr: ptrToMap(map[string]interface{}{"X": float32(3), "Y": int16(4), "Z": 1.5}), - out: ptrToMap(map[string]interface{}{"X": float64(1), "Y": float64(2), "Z": 1.5}), + ptr: ptrToMap(map[string]any{"X": float32(3), "Y": int16(4), "Z": 1.5}), + out: ptrToMap(map[string]any{"X": float64(1), "Y": float64(2), "Z": 1.5}), }, } @@ -1983,7 +1983,7 @@ func TestPrefilled(t *testing.T) { } var invalidUnmarshalTests = []struct { - v interface{} + v any want string }{ {nil, "json: Unmarshal(nil)"}, @@ -2006,7 +2006,7 @@ func TestInvalidUnmarshal(t *testing.T) { } var invalidUnmarshalTextTests = []struct { - v interface{} + v any want string }{ {nil, "json: Unmarshal(nil)"}, @@ -2038,7 +2038,7 @@ func TestInvalidStringOption(t *testing.T) { M map[string]string `json:",string"` S []string `json:",string"` A [1]string `json:",string"` - I interface{} `json:",string"` + I any `json:",string"` P *int `json:",string"` }{M: make(map[string]string), S: make([]string, 0), I: num, P: &num} diff --git a/encode.go b/encode.go index 37fd0f1..a20a6d0 100644 --- a/encode.go +++ b/encode.go @@ -159,7 +159,7 @@ import ( // JSON cannot represent cyclic data structures and Marshal does not // handle them. Passing cyclic structures to Marshal will result in // an infinite recursion. -func Marshal(v interface{}) ([]byte, error) { +func Marshal(v any) ([]byte, error) { e := &encodeState{} err := e.marshal(v, encOpts{escapeHTML: true}) if err != nil { @@ -169,7 +169,7 @@ func Marshal(v interface{}) ([]byte, error) { } // MarshalIndent is like Marshal but applies Indent to format the output. -func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) { +func MarshalIndent(v any, prefix, indent string) ([]byte, error) { b, err := Marshal(v) if err != nil { return nil, err @@ -284,7 +284,7 @@ func newEncodeState() *encodeState { return new(encodeState) } -func (e *encodeState) marshal(v interface{}, opts encOpts) (err error) { +func (e *encodeState) marshal(v any, opts encOpts) (err error) { defer func() { if r := recover(); r != nil { if _, ok := r.(runtime.Error); ok { diff --git a/encode_test.go b/encode_test.go index d433908..1bcec6c 100644 --- a/encode_test.go +++ b/encode_test.go @@ -27,8 +27,8 @@ type Optionals struct { Slr []string `json:"slr,random"` Slo []string `json:"slo,omitempty"` - Mr map[string]interface{} `json:"mr"` - Mo map[string]interface{} `json:",omitempty"` + Mr map[string]any `json:"mr"` + Mo map[string]any `json:",omitempty"` Fr float64 `json:"fr"` Fo float64 `json:"fo,omitempty"` @@ -58,8 +58,8 @@ var optionalsExpected = `{ func TestOmitEmpty(t *testing.T) { var o Optionals o.Sw = "something" - o.Mr = map[string]interface{}{} - o.Mo = map[string]interface{}{} + o.Mr = map[string]any{} + o.Mo = map[string]any{} got, err := MarshalIndent(&o, "", " ") if err != nil { @@ -131,7 +131,7 @@ func TestEncodeRenamedByteSlice(t *testing.T) { } } -var unsupportedValues = []interface{}{ +var unsupportedValues = []any{ math.NaN(), math.Inf(-1), math.Inf(1), @@ -372,19 +372,19 @@ func (nm *nilMarshaler) MarshalJSON() ([]byte, error) { // Issue 16042. func TestNilMarshal(t *testing.T) { testCases := []struct { - v interface{} + v any want string }{ {v: nil, want: `null`}, {v: new(float64), want: `0`}, - {v: []interface{}(nil), want: `null`}, + {v: []any(nil), want: `null`}, {v: []string(nil), want: `null`}, {v: map[string]string(nil), want: `null`}, {v: []byte(nil), want: `null`}, {v: struct{ M string }{"gopher"}, want: `{"M":"gopher"}`}, {v: struct{ M Marshaler }{}, want: `{"M":null}`}, {v: struct{ M Marshaler }{(*nilMarshaler)(nil)}, want: `{"M":"0zenil0"}`}, - {v: struct{ M interface{} }{(*nilMarshaler)(nil)}, want: `{"M":null}`}, + {v: struct{ M any }{(*nilMarshaler)(nil)}, want: `{"M":null}`}, } for _, tt := range testCases { @@ -619,7 +619,7 @@ type textint int func (i textint) MarshalText() ([]byte, error) { return tenc(`TI:%d`, i) } -func tenc(format string, a ...interface{}) ([]byte, error) { +func tenc(format string, a ...any) ([]byte, error) { var buf bytes.Buffer fmt.Fprintf(&buf, format, a...) return buf.Bytes(), nil @@ -628,7 +628,7 @@ func tenc(format string, a ...interface{}) ([]byte, error) { // Issue 13783 func TestEncodeBytekind(t *testing.T) { testdata := []struct { - data interface{} + data any want string }{ {byte(7), "7"}, @@ -701,7 +701,7 @@ func TestMarshalFloat(t *testing.T) { t.Parallel() nfail := 0 test := func(f float64, bits int) { - vf := interface{}(f) + vf := any(f) if bits == 32 { f = float64(float32(f)) // round vf = float32(f) @@ -794,25 +794,25 @@ func TestMarshalRawMessageValue(t *testing.T) { ) tests := []struct { - in interface{} + in any want string ok bool }{ // Test with nil RawMessage. {rawNil, "null", true}, {&rawNil, "null", true}, - {[]interface{}{rawNil}, "[null]", true}, - {&[]interface{}{rawNil}, "[null]", true}, - {[]interface{}{&rawNil}, "[null]", true}, - {&[]interface{}{&rawNil}, "[null]", true}, + {[]any{rawNil}, "[null]", true}, + {&[]any{rawNil}, "[null]", true}, + {[]any{&rawNil}, "[null]", true}, + {&[]any{&rawNil}, "[null]", true}, {struct{ M RawMessage }{rawNil}, `{"M":null}`, true}, {&struct{ M RawMessage }{rawNil}, `{"M":null}`, true}, {struct{ M *RawMessage }{&rawNil}, `{"M":null}`, true}, {&struct{ M *RawMessage }{&rawNil}, `{"M":null}`, true}, - {map[string]interface{}{"M": rawNil}, `{"M":null}`, true}, - {&map[string]interface{}{"M": rawNil}, `{"M":null}`, true}, - {map[string]interface{}{"M": &rawNil}, `{"M":null}`, true}, - {&map[string]interface{}{"M": &rawNil}, `{"M":null}`, true}, + {map[string]any{"M": rawNil}, `{"M":null}`, true}, + {&map[string]any{"M": rawNil}, `{"M":null}`, true}, + {map[string]any{"M": &rawNil}, `{"M":null}`, true}, + {&map[string]any{"M": &rawNil}, `{"M":null}`, true}, {T1{rawNil}, "{}", true}, {T2{&rawNil}, `{"M":null}`, true}, {&T1{rawNil}, "{}", true}, @@ -821,18 +821,18 @@ func TestMarshalRawMessageValue(t *testing.T) { // Test with empty, but non-nil, RawMessage. {rawEmpty, "", false}, {&rawEmpty, "", false}, - {[]interface{}{rawEmpty}, "", false}, - {&[]interface{}{rawEmpty}, "", false}, - {[]interface{}{&rawEmpty}, "", false}, - {&[]interface{}{&rawEmpty}, "", false}, + {[]any{rawEmpty}, "", false}, + {&[]any{rawEmpty}, "", false}, + {[]any{&rawEmpty}, "", false}, + {&[]any{&rawEmpty}, "", false}, {struct{ X RawMessage }{rawEmpty}, "", false}, {&struct{ X RawMessage }{rawEmpty}, "", false}, {struct{ X *RawMessage }{&rawEmpty}, "", false}, {&struct{ X *RawMessage }{&rawEmpty}, "", false}, - {map[string]interface{}{"nil": rawEmpty}, "", false}, - {&map[string]interface{}{"nil": rawEmpty}, "", false}, - {map[string]interface{}{"nil": &rawEmpty}, "", false}, - {&map[string]interface{}{"nil": &rawEmpty}, "", false}, + {map[string]any{"nil": rawEmpty}, "", false}, + {&map[string]any{"nil": rawEmpty}, "", false}, + {map[string]any{"nil": &rawEmpty}, "", false}, + {&map[string]any{"nil": &rawEmpty}, "", false}, {T1{rawEmpty}, "{}", true}, {T2{&rawEmpty}, "", false}, {&T1{rawEmpty}, "{}", true}, @@ -845,18 +845,18 @@ func TestMarshalRawMessageValue(t *testing.T) { // See https://github.com/golang/go/issues/14493#issuecomment-255857318 {rawText, `"foo"`, true}, // Issue6458 {&rawText, `"foo"`, true}, - {[]interface{}{rawText}, `["foo"]`, true}, // Issue6458 - {&[]interface{}{rawText}, `["foo"]`, true}, // Issue6458 - {[]interface{}{&rawText}, `["foo"]`, true}, - {&[]interface{}{&rawText}, `["foo"]`, true}, + {[]any{rawText}, `["foo"]`, true}, // Issue6458 + {&[]any{rawText}, `["foo"]`, true}, // Issue6458 + {[]any{&rawText}, `["foo"]`, true}, + {&[]any{&rawText}, `["foo"]`, true}, {struct{ M RawMessage }{rawText}, `{"M":"foo"}`, true}, // Issue6458 {&struct{ M RawMessage }{rawText}, `{"M":"foo"}`, true}, {struct{ M *RawMessage }{&rawText}, `{"M":"foo"}`, true}, {&struct{ M *RawMessage }{&rawText}, `{"M":"foo"}`, true}, - {map[string]interface{}{"M": rawText}, `{"M":"foo"}`, true}, // Issue6458 - {&map[string]interface{}{"M": rawText}, `{"M":"foo"}`, true}, // Issue6458 - {map[string]interface{}{"M": &rawText}, `{"M":"foo"}`, true}, - {&map[string]interface{}{"M": &rawText}, `{"M":"foo"}`, true}, + {map[string]any{"M": rawText}, `{"M":"foo"}`, true}, // Issue6458 + {&map[string]any{"M": rawText}, `{"M":"foo"}`, true}, // Issue6458 + {map[string]any{"M": &rawText}, `{"M":"foo"}`, true}, + {&map[string]any{"M": &rawText}, `{"M":"foo"}`, true}, {T1{rawText}, `{"M":"foo"}`, true}, // Issue6458 {T2{&rawText}, `{"M":"foo"}`, true}, {&T1{rawText}, `{"M":"foo"}`, true}, @@ -881,11 +881,11 @@ func TestMarshalRawMessageValue(t *testing.T) { type ObjEnc struct { A int B OrderedObject - C interface{} + C any } var orderedObjectTests = []struct { - in interface{} + in any out string }{ {OrderedObject{}, `{}`}, @@ -894,7 +894,7 @@ var orderedObjectTests = []struct { {ObjEnc{A: 234, B: OrderedObject{}, C: OrderedObject{{"A", 0}}}, `{"A":234,"B":{},"C":{"A":0}}`}, {[]OrderedObject{{{"A", "Ay"}, {"B", "Bee"}}, {{"A", "Nay"}}}, `[{"A":"Ay","B":"Bee"},{"A":"Nay"}]`}, {map[string]OrderedObject{"A": {{"A", "Ay"}, {"B", "Bee"}}}, `{"A":{"A":"Ay","B":"Bee"}}`}, - {map[string]interface{}{"A": OrderedObject{{"A", "Ay"}, {"B", "Bee"}}}, `{"A":{"A":"Ay","B":"Bee"}}`}, + {map[string]any{"A": OrderedObject{{"A", "Ay"}, {"B", "Bee"}}}, `{"A":{"A":"Ay","B":"Bee"}}`}, } func TestEncodeOrderedObject(t *testing.T) { diff --git a/example_test.go b/example_test.go index 62817df..c336eb1 100644 --- a/example_test.go +++ b/example_test.go @@ -203,7 +203,7 @@ func ExampleRawMessage_unmarshal() { } for _, c := range colors { - var dst interface{} + var dst any switch c.Space { case "RGB": dst = new(RGB) diff --git a/scanner_test.go b/scanner_test.go index 0d4518a..a926812 100644 --- a/scanner_test.go +++ b/scanner_test.go @@ -273,7 +273,7 @@ func initBig() { jsonBig = b } -func genValue(n int) interface{} { +func genValue(n int) any { if n > 1 { switch rand.Intn(2) { case 0: @@ -306,7 +306,7 @@ func genString(stddev float64) string { return string(c) } -func genArray(n int) []interface{} { +func genArray(n int) []any { f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2))) if f > n { f = n @@ -314,14 +314,14 @@ func genArray(n int) []interface{} { if f < 1 { f = 1 } - x := make([]interface{}, f) + x := make([]any, f) for i := range x { x[i] = genValue(((i+1)*n)/f - (i*n)/f) } return x } -func genMap(n int) map[string]interface{} { +func genMap(n int) map[string]any { f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2))) if f > n { f = n @@ -329,7 +329,7 @@ func genMap(n int) map[string]interface{} { if n > 0 && f == 0 { f = 1 } - x := make(map[string]interface{}) + x := make(map[string]any) for i := 0; i < f; i++ { x[genString(10)] = genValue(((i+1)*n)/f - (i*n)/f) } diff --git a/stream.go b/stream.go index 44a92d1..3e90197 100644 --- a/stream.go +++ b/stream.go @@ -31,12 +31,12 @@ func NewDecoder(r io.Reader) *Decoder { return &Decoder{r: r} } -// UseNumber causes the Decoder to unmarshal a number into an interface{} as a +// UseNumber causes the Decoder to unmarshal a number into an any as a // Number instead of as a float64. func (dec *Decoder) UseNumber() { dec.d.useNumber = true } -// UseOrderedObject causes the Decoder to unmarshal an object into an interface{} -// as a OrderedObject instead of as a map[string]interface{}. +// UseOrderedObject causes the Decoder to unmarshal an object into an any +// as a OrderedObject instead of as a map[string]any. func (dec *Decoder) UseOrderedObject() { dec.d.useOrderedObject = true } // Decode reads the next JSON-encoded value from its @@ -44,7 +44,7 @@ func (dec *Decoder) UseOrderedObject() { dec.d.useOrderedObject = true } // // See the documentation for Unmarshal for details about // the conversion of JSON into a Go value. -func (dec *Decoder) Decode(v interface{}) error { +func (dec *Decoder) Decode(v any) error { if dec.err != nil { return dec.err } @@ -189,7 +189,7 @@ func NewEncoder(w io.Writer) *Encoder { // // See the documentation for Marshal for details about the // conversion of Go values to JSON. -func (enc *Encoder) Encode(v interface{}) error { +func (enc *Encoder) Encode(v any) error { if enc.err != nil { return enc.err } @@ -273,7 +273,7 @@ var _ Unmarshaler = (*RawMessage)(nil) // Member is used to store key/value pairs in an OrderedObject. type Member struct { Key string - Value interface{} + Value any } // OrderedObject stores the key/value pairs of a JSON object in the order @@ -288,9 +288,9 @@ type Member struct { // var oa []OrderedObject // Unmarshal(json, &oa) // decode an array of JSON objects, while preserving key order // -// var v interface{} +// var v any // d := new Decoder(json) -// d.UseOrderedObject() // decode all JSON objects as OrderedObject rather than map[string]interface{} +// d.UseOrderedObject() // decode all JSON objects as OrderedObject rather than map[string]any // d.Decode(&v) // // type A struct { @@ -327,7 +327,7 @@ type OrderedObject []Member // Number, for JSON numbers // string, for JSON string literals // nil, for JSON null -type Token interface{} +type Token any const ( tokenTopValue = iota @@ -492,7 +492,7 @@ func (dec *Decoder) Token() (Token, error) { if !dec.tokenValueAllowed() { return dec.tokenError(c) } - var x interface{} + var x any if err := dec.Decode(&x); err != nil { clearOffset(err) return nil, err diff --git a/stream_test.go b/stream_test.go index 2a71bee..235a4fa 100644 --- a/stream_test.go +++ b/stream_test.go @@ -19,14 +19,14 @@ import ( // Test values for the stream test. // One of each JSON kind. -var streamTest = []interface{}{ +var streamTest = []any{ 0.1, "hello", nil, true, false, - []interface{}{"a", "b", "c"}, - map[string]interface{}{"K": "Kelvin", "ß": "long s"}, + []any{"a", "b", "c"}, + map[string]any{"K": "Kelvin", "ß": "long s"}, 3.14, // another value to make sure something can follow map } @@ -95,7 +95,7 @@ func TestEncoderSetEscapeHTML(t *testing.T) { var ct CText for _, tt := range []struct { name string - v interface{} + v any wantEscape string want string }{ @@ -136,7 +136,7 @@ func TestDecoder(t *testing.T) { buf.WriteRune(c) } } - out := make([]interface{}, i) + out := make([]any, i) dec := NewDecoder(&buf) for j := range out { if err := dec.Decode(&out[j]); err != nil { @@ -250,7 +250,7 @@ func TestBlocking(t *testing.T) { for _, enc := range blockingTests { r, w := net.Pipe() go w.Write([]byte(enc)) - var val interface{} + var val any // If Decode reads beyond what w.Write writes above, // it will block, and the test will deadlock. @@ -279,72 +279,72 @@ func BenchmarkEncoderEncode(b *testing.B) { type tokenStreamCase struct { json string - expTokens []interface{} + expTokens []any } type decodeThis struct { - v interface{} + v any } var tokenStreamCases []tokenStreamCase = []tokenStreamCase{ // streaming token cases - {json: `10`, expTokens: []interface{}{float64(10)}}, - {json: ` [10] `, expTokens: []interface{}{ + {json: `10`, expTokens: []any{float64(10)}}, + {json: ` [10] `, expTokens: []any{ Delim('['), float64(10), Delim(']')}}, - {json: ` [false,10,"b"] `, expTokens: []interface{}{ + {json: ` [false,10,"b"] `, expTokens: []any{ Delim('['), false, float64(10), "b", Delim(']')}}, - {json: `{ "a": 1 }`, expTokens: []interface{}{ + {json: `{ "a": 1 }`, expTokens: []any{ Delim('{'), "a", float64(1), Delim('}')}}, - {json: `{"a": 1, "b":"3"}`, expTokens: []interface{}{ + {json: `{"a": 1, "b":"3"}`, expTokens: []any{ Delim('{'), "a", float64(1), "b", "3", Delim('}')}}, - {json: ` [{"a": 1},{"a": 2}] `, expTokens: []interface{}{ + {json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{ Delim('['), Delim('{'), "a", float64(1), Delim('}'), Delim('{'), "a", float64(2), Delim('}'), Delim(']')}}, - {json: `{"obj": {"a": 1}}`, expTokens: []interface{}{ + {json: `{"obj": {"a": 1}}`, expTokens: []any{ Delim('{'), "obj", Delim('{'), "a", float64(1), Delim('}'), Delim('}')}}, - {json: `{"obj": [{"a": 1}]}`, expTokens: []interface{}{ + {json: `{"obj": [{"a": 1}]}`, expTokens: []any{ Delim('{'), "obj", Delim('['), Delim('{'), "a", float64(1), Delim('}'), Delim(']'), Delim('}')}}, // streaming tokens with intermittent Decode() - {json: `{ "a": 1 }`, expTokens: []interface{}{ + {json: `{ "a": 1 }`, expTokens: []any{ Delim('{'), "a", decodeThis{float64(1)}, Delim('}')}}, - {json: ` [ { "a" : 1 } ] `, expTokens: []interface{}{ + {json: ` [ { "a" : 1 } ] `, expTokens: []any{ Delim('['), - decodeThis{map[string]interface{}{"a": float64(1)}}, + decodeThis{map[string]any{"a": float64(1)}}, Delim(']')}}, - {json: ` [{"a": 1},{"a": 2}] `, expTokens: []interface{}{ + {json: ` [{"a": 1},{"a": 2}] `, expTokens: []any{ Delim('['), - decodeThis{map[string]interface{}{"a": float64(1)}}, - decodeThis{map[string]interface{}{"a": float64(2)}}, + decodeThis{map[string]any{"a": float64(1)}}, + decodeThis{map[string]any{"a": float64(2)}}, Delim(']')}}, - {json: `{ "obj" : [ { "a" : 1 } ] }`, expTokens: []interface{}{ + {json: `{ "obj" : [ { "a" : 1 } ] }`, expTokens: []any{ Delim('{'), "obj", Delim('['), - decodeThis{map[string]interface{}{"a": float64(1)}}, + decodeThis{map[string]any{"a": float64(1)}}, Delim(']'), Delim('}')}}, - {json: `{"obj": {"a": 1}}`, expTokens: []interface{}{ + {json: `{"obj": {"a": 1}}`, expTokens: []any{ Delim('{'), "obj", - decodeThis{map[string]interface{}{"a": float64(1)}}, + decodeThis{map[string]any{"a": float64(1)}}, Delim('}')}}, - {json: `{"obj": [{"a": 1}]}`, expTokens: []interface{}{ + {json: `{"obj": [{"a": 1}]}`, expTokens: []any{ Delim('{'), "obj", - decodeThis{[]interface{}{ - map[string]interface{}{"a": float64(1)}, + decodeThis{[]any{ + map[string]any{"a": float64(1)}, }}, Delim('}')}}, - {json: ` [{"a": 1} {"a": 2}] `, expTokens: []interface{}{ + {json: ` [{"a": 1} {"a": 2}] `, expTokens: []any{ Delim('['), - decodeThis{map[string]interface{}{"a": float64(1)}}, + decodeThis{map[string]any{"a": float64(1)}}, decodeThis{&SyntaxError{"expected comma after array element", 0}}, }}, - {json: `{ "a" 1 }`, expTokens: []interface{}{ + {json: `{ "a" 1 }`, expTokens: []any{ Delim('{'), "a", decodeThis{&SyntaxError{"expected colon after object key", 0}}, }}, @@ -357,7 +357,7 @@ func TestDecodeInStream(t *testing.T) { dec := NewDecoder(strings.NewReader(tcase.json)) for i, etk := range tcase.expTokens { - var tk interface{} + var tk any var err error if dt, ok := etk.(decodeThis); ok { diff --git a/tagkey_test.go b/tagkey_test.go index f77c49c..9678097 100644 --- a/tagkey_test.go +++ b/tagkey_test.go @@ -73,7 +73,7 @@ type unicodeTag struct { } var structTagObjectKeyTests = []struct { - raw interface{} + raw any value string key string }{ @@ -101,12 +101,12 @@ func TestStructTagObjectKey(t *testing.T) { if err != nil { t.Fatalf("Marshal(%#q) failed: %v", tt.raw, err) } - var f interface{} + var f any err = Unmarshal(b, &f) if err != nil { t.Fatalf("Unmarshal(%#q) failed: %v", b, err) } - for i, v := range f.(map[string]interface{}) { + for i, v := range f.(map[string]any) { switch i { case tt.key: if s, ok := v.(string); !ok || s != tt.value { From 551baa5ae378d2ee4bd40a1732a146619fb350d7 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 29 Feb 2024 15:45:24 +0300 Subject: [PATCH 23/67] encode: check Write error encode.go:743:12 errcheck Error return value of `enc.Write` is not checked It's not very likely (bytes.ErrTooLarge maybe), but this code runs inside a defer/recover, so we can handle it this way. Signed-off-by: Roman Khimov --- encode.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/encode.go b/encode.go index a20a6d0..adbefec 100644 --- a/encode.go +++ b/encode.go @@ -740,7 +740,10 @@ func encodeByteSlice(e *encodeState, v reflect.Value, _ encOpts) { // for large buffers, avoid unnecessary extra temporary // buffer space. enc := base64.NewEncoder(base64.StdEncoding, e) - enc.Write(s) + _, err := enc.Write(s) + if err != nil { + panic(err) + } enc.Close() } e.WriteByte('"') From 1205ed9097818e16bf33278f23714f2760f13f3f Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 29 Feb 2024 15:52:14 +0300 Subject: [PATCH 24/67] *: fix godot warnings decode.go:308:85 godot Comment should end in a period decode.go:1012:48 godot Comment should end in a period scanner.go:596:53 godot Comment should end in a period stream.go:344:62 godot Comment should end in a period Signed-off-by: Roman Khimov --- decode.go | 4 ++-- scanner.go | 2 +- stream.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/decode.go b/decode.go index bbcd2b8..537bd62 100644 --- a/decode.go +++ b/decode.go @@ -305,7 +305,7 @@ func (d *decodeState) saveError(err error) { } } -// addErrorContext returns a new error enhanced with information from d.errorContext +// addErrorContext returns a new error enhanced with information from d.errorContext. func (d *decodeState) addErrorContext(err error) error { if d.errorContext.Struct != "" || d.errorContext.Field != "" { switch err := err.(type) { @@ -1009,7 +1009,7 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool // in an empty interface. They are not strictly necessary, // but they avoid the weight of reflection in this common case. -// valueInterface is like value but returns any +// valueInterface is like value but returns any. func (d *decodeState) valueInterface() any { switch d.scanWhile(scanSkipSpace) { default: diff --git a/scanner.go b/scanner.go index ae34418..f7cfda0 100644 --- a/scanner.go +++ b/scanner.go @@ -593,7 +593,7 @@ func (s *scanner) error(c byte, context string) int { return scanError } -// quoteChar formats c as a quoted character literal +// quoteChar formats c as a quoted character literal. func quoteChar(c byte) string { // special cases - different from quoted strings if c == '\'' { diff --git a/stream.go b/stream.go index 3e90197..5fe2753 100644 --- a/stream.go +++ b/stream.go @@ -341,7 +341,7 @@ const ( tokenObjectComma ) -// advance tokenstate from a separator state to a value state +// advance tokenstate from a separator state to a value state. func (dec *Decoder) tokenPrepareForDecode() error { // Note: Not calling peek before switch, to avoid // putting peek into the standard Decode path. From 19c018b4edaeaf3968f8b61ef03b1a0d67d9aed3 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 29 Feb 2024 15:53:05 +0300 Subject: [PATCH 25/67] fold: fix whitespace error fold.go:99:2 whitespace unnecessary trailing newline Signed-off-by: Roman Khimov --- fold.go | 1 - 1 file changed, 1 deletion(-) diff --git a/fold.go b/fold.go index ab249b2..4503239 100644 --- a/fold.go +++ b/fold.go @@ -95,7 +95,6 @@ func equalFoldRight(s, t []byte) bool { return false } t = t[size:] - } if len(t) > 0 { return false From 7ff1a6894b5f46e257c3a1dba739f1722b967be9 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 29 Feb 2024 15:53:53 +0300 Subject: [PATCH 26/67] fold: simplify code fold.go:99:2 gosimple S1008: should use 'return len(t) <= 0' instead of 'if len(t) > 0 { return false }; return true' Signed-off-by: Roman Khimov --- fold.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/fold.go b/fold.go index 4503239..520b5a8 100644 --- a/fold.go +++ b/fold.go @@ -96,10 +96,7 @@ func equalFoldRight(s, t []byte) bool { } t = t[size:] } - if len(t) > 0 { - return false - } - return true + return len(t) <= 0 } // asciiEqualFold is a specialization of bytes.EqualFold for use when From a4ef347bbc2d9dc95e3a5037b084e1121009b58d Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 29 Feb 2024 15:55:02 +0300 Subject: [PATCH 27/67] encode: don't initialize count encode.go:1139:2 ineffassign ineffectual assignment to count Signed-off-by: Roman Khimov --- encode.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/encode.go b/encode.go index adbefec..d4e39a1 100644 --- a/encode.go +++ b/encode.go @@ -1136,7 +1136,7 @@ func typeFields(t reflect.Type) []field { next := []field{{typ: t}} // Count of queued names for current level and the next. - count := map[reflect.Type]int{} + var count map[reflect.Type]int nextCount := map[reflect.Type]int{} // Types already visited at an earlier level. From c406b37478706212bddd8de3434a44e76a08ef59 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 29 Feb 2024 15:56:24 +0300 Subject: [PATCH 28/67] *: fix exhaustive linter warnings decode.go:882:3 exhaustive missing cases in switch of type reflect.Kind: reflect.Invalid, reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128, reflect.Array, reflect.Chan, reflect.Func, reflect.String, reflect.Struct, reflect.UnsafePointer encode.go:308:2 exhaustive missing cases in switch of type reflect.Kind: reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer encode.go:891:2 exhaustive missing cases in switch of type reflect.Kind: reflect.Invalid, reflect.Bool, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128, reflect.Array, reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer|reflect.Ptr, reflect.Slice, reflect.String, reflect.Struct, reflect.UnsafePointer encode.go:1185:6 exhaustive missing cases in switch of type reflect.Kind: reflect.Invalid, reflect.Uintptr, reflect.Complex64, reflect.Complex128, reflect.Array, reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer|reflect.Ptr, reflect.Slice, reflect.Struct, reflect.UnsafePointer Signed-off-by: Roman Khimov --- decode.go | 1 + encode.go | 3 +++ 2 files changed, 4 insertions(+) diff --git a/decode.go b/decode.go index 537bd62..efdfc6b 100644 --- a/decode.go +++ b/decode.go @@ -883,6 +883,7 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice: v.Set(reflect.Zero(v.Type())) // otherwise, ignore null for primitives/string + default: } case 't', 'f': // true, false value := item[0] == 't' diff --git a/encode.go b/encode.go index d4e39a1..f971e42 100644 --- a/encode.go +++ b/encode.go @@ -318,6 +318,7 @@ func isEmptyValue(v reflect.Value) bool { return v.Float() == 0 case reflect.Interface, reflect.Ptr: return v.IsNil() + default: } return false } @@ -895,6 +896,7 @@ func (w *reflectWithString) resolve() error { case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: w.s = strconv.FormatUint(w.v.Uint(), 10) return nil + default: } panic("unexpected map key type") } @@ -1189,6 +1191,7 @@ func typeFields(t reflect.Type) []field { reflect.Float32, reflect.Float64, reflect.String: quoted = true + default: } } From c0749cff7714e3f654adf544503ce1386a3ee300 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 29 Feb 2024 15:57:22 +0300 Subject: [PATCH 29/67] workflows: drop wrong coverage package limitations They're copy-pasted from NeoGo, but they're not relevant here. Signed-off-by: Roman Khimov --- .github/workflows/run_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 0377f8e..6554077 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -30,7 +30,7 @@ jobs: go-version: 1.22 - name: Write coverage profile - run: go test -v ./... -coverprofile=./coverage.txt -covermode=atomic -coverpkg=./pkg...,./cli/... + run: go test -v ./... -coverprofile=./coverage.txt -covermode=atomic - name: Upload coverage results to Codecov uses: codecov/codecov-action@v2 From d0a949ccd2cd2d20c8188727270700e3a9840381 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 29 Feb 2024 15:58:31 +0300 Subject: [PATCH 30/67] workflows: add lint/codeql We care about code we do. Signed-off-by: Roman Khimov --- .github/workflows/run_tests.yml | 59 +++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 6554077..2df59a6 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -76,3 +76,62 @@ jobs: - name: Run tests run: go test -v -race ./... + + lint: + name: Lint + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + - name: golangci-lint + uses: golangci/golangci-lint-action@v4 + with: + version: latest + + codeql: + name: CodeQL + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + language: [ 'go' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 From 04b64f92410fc85296a18c8e4dd43c7f14205eb4 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 29 Feb 2024 16:07:26 +0300 Subject: [PATCH 31/67] decode_test: fix unused linter warnings Signed-off-by: Roman Khimov --- decode_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/decode_test.go b/decode_test.go index 92a4b5f..1cbb30c 100644 --- a/decode_test.go +++ b/decode_test.go @@ -58,7 +58,7 @@ var ifaceNumAsNumber = map[string]any{ } type tx struct { - x int + x int //nolint:unused // Used via reflection. } type u8 uint8 @@ -1207,7 +1207,7 @@ type All struct { Interface any PInterface *any - unexported int + unexported int //nolint:unused // Not really used, but needed for test. } type Small struct { @@ -1889,8 +1889,8 @@ func TestUnmarshalSyntax(t *testing.T) { // Issue 4660 type unexportedFields struct { Name string - m map[string]any `json:"-"` - m2 map[string]any `json:"abcd"` + m map[string]any `json:"-"` //nolint:unused // Not really used, but important for test. + m2 map[string]any `json:"abcd"` //nolint:unused // Not really used, but important for test. } func TestUnmarshalUnexported(t *testing.T) { From 7cf27c7cc53151f70ea222a4fdbbbc6a47f73380 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 29 Feb 2024 16:07:42 +0300 Subject: [PATCH 32/67] decode_test: go fmt Signed-off-by: Roman Khimov --- decode_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/decode_test.go b/decode_test.go index 1cbb30c..bcae0d7 100644 --- a/decode_test.go +++ b/decode_test.go @@ -1622,7 +1622,7 @@ type NullTestStrings struct { PBool *bool `json:",string"` Map map[string]string `json:",string"` Slice []string `json:",string"` - Interface any `json:",string"` + Interface any `json:",string"` PRaw *RawMessage `json:",string"` PTime *time.Time `json:",string"` @@ -2038,7 +2038,7 @@ func TestInvalidStringOption(t *testing.T) { M map[string]string `json:",string"` S []string `json:",string"` A [1]string `json:",string"` - I any `json:",string"` + I any `json:",string"` P *int `json:",string"` }{M: make(map[string]string), S: make([]string, 0), I: num, P: &num} From 752aa208eadf569cf87982a2a914252d380d1a0f Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 29 Feb 2024 16:12:33 +0300 Subject: [PATCH 33/67] *: fix errcheck warnings In most cases these values are irrelevant, but some tests can be improved. Signed-off-by: Roman Khimov --- example_test.go | 4 ++-- scanner_test.go | 2 +- stream_test.go | 17 ++++++++++++++--- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/example_test.go b/example_test.go index c336eb1..d85f73b 100644 --- a/example_test.go +++ b/example_test.go @@ -261,8 +261,8 @@ func ExampleIndent() { } var out bytes.Buffer - json.Indent(&out, b, "=", "\t") - out.WriteTo(os.Stdout) + _ = json.Indent(&out, b, "=", "\t") + _, _ = out.WriteTo(os.Stdout) // Output: // [ // = { diff --git a/scanner_test.go b/scanner_test.go index a926812..50d43ca 100644 --- a/scanner_test.go +++ b/scanner_test.go @@ -232,7 +232,7 @@ func BenchmarkSkipValue(b *testing.B) { initBig() b.ResetTimer() for i := 0; i < b.N; i++ { - nextValue(jsonBig, &benchScan) + _, _, _ = nextValue(jsonBig, &benchScan) } b.SetBytes(int64(len(jsonBig))) } diff --git a/stream_test.go b/stream_test.go index 235a4fa..2f7b0a3 100644 --- a/stream_test.go +++ b/stream_test.go @@ -82,7 +82,10 @@ func TestEncoderIndent(t *testing.T) { enc := NewEncoder(&buf) enc.SetIndent(">", ".") for _, v := range streamTest { - enc.Encode(v) + err := enc.Encode(v) + if err != nil { + t.Error(err) + } } if have, want := buf.String(), streamEncodedIndent; have != want { t.Error("indented encoding mismatch") @@ -249,7 +252,12 @@ var blockingTests = []string{ func TestBlocking(t *testing.T) { for _, enc := range blockingTests { r, w := net.Pipe() - go w.Write([]byte(enc)) + go func() { + _, err := w.Write([]byte(enc)) + if err != nil { + t.Error(err) + } + }() var val any // If Decode reads beyond what w.Write writes above, @@ -392,7 +400,10 @@ func TestHTTPDecoding(t *testing.T) { const raw = `{ "foo": "bar" }` ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(raw)) + _, err := w.Write([]byte(raw)) + if err != nil { + t.Error(err) + } })) defer ts.Close() res, err := http.Get(ts.URL) From ef71ecc59d7989e2088c31f454ae51b0a1413860 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 29 Feb 2024 16:19:23 +0300 Subject: [PATCH 34/67] *: suppress staticcheck/govet linters where we intentionally have bad code Signed-off-by: Roman Khimov --- decode_test.go | 46 +++++++++++++++++++++++----------------------- encode_test.go | 2 +- tagkey_test.go | 4 ++-- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/decode_test.go b/decode_test.go index bcae0d7..530e653 100644 --- a/decode_test.go +++ b/decode_test.go @@ -1159,7 +1159,7 @@ type All struct { Float64 float64 Foo string `json:"bar"` - Foo2 string `json:"bar2,dummyopt"` + Foo2 string `json:"bar2,dummyopt"` //nolint:staticcheck // It's intentionally wrong. IntStr int64 `json:",string"` @@ -1620,23 +1620,23 @@ type NullTestStrings struct { Float64 float64 `json:",string"` String string `json:",string"` PBool *bool `json:",string"` - Map map[string]string `json:",string"` - Slice []string `json:",string"` - Interface any `json:",string"` + Map map[string]string `json:",string"` //nolint:staticcheck // It's intentionally wrong. + Slice []string `json:",string"` //nolint:staticcheck // It's intentionally wrong. + Interface any `json:",string"` //nolint:staticcheck // It's intentionally wrong. - PRaw *RawMessage `json:",string"` - PTime *time.Time `json:",string"` - PBigInt *big.Int `json:",string"` - PText *MustNotUnmarshalText `json:",string"` - PBuffer *bytes.Buffer `json:",string"` - PStruct *struct{} `json:",string"` + PRaw *RawMessage `json:",string"` //nolint:staticcheck // It's intentionally wrong. + PTime *time.Time `json:",string"` //nolint:staticcheck // It's intentionally wrong. + PBigInt *big.Int `json:",string"` //nolint:staticcheck // It's intentionally wrong. + PText *MustNotUnmarshalText `json:",string"` //nolint:staticcheck // It's intentionally wrong. + PBuffer *bytes.Buffer `json:",string"` //nolint:staticcheck // It's intentionally wrong. + PStruct *struct{} `json:",string"` //nolint:staticcheck // It's intentionally wrong. - Raw RawMessage `json:",string"` - Time time.Time `json:",string"` - BigInt big.Int `json:",string"` - Text MustNotUnmarshalText `json:",string"` - Buffer bytes.Buffer `json:",string"` - Struct struct{} `json:",string"` + Raw RawMessage `json:",string"` //nolint:staticcheck // It's intentionally wrong. + Time time.Time `json:",string"` //nolint:staticcheck // It's intentionally wrong. + BigInt big.Int `json:",string"` //nolint:staticcheck // It's intentionally wrong. + Text MustNotUnmarshalText `json:",string"` //nolint:staticcheck // It's intentionally wrong. + Buffer bytes.Buffer `json:",string"` //nolint:staticcheck // It's intentionally wrong. + Struct struct{} `json:",string"` //nolint:staticcheck // It's intentionally wrong. } // JSON null values should be ignored for primitives and string values instead of resulting in an error. @@ -1890,7 +1890,7 @@ func TestUnmarshalSyntax(t *testing.T) { type unexportedFields struct { Name string m map[string]any `json:"-"` //nolint:unused // Not really used, but important for test. - m2 map[string]any `json:"abcd"` //nolint:unused // Not really used, but important for test. + m2 map[string]any `json:"abcd"` //nolint:unused,govet // Not really used and wrong, but important for test. } func TestUnmarshalUnexported(t *testing.T) { @@ -2034,12 +2034,12 @@ func TestInvalidUnmarshalText(t *testing.T) { func TestInvalidStringOption(t *testing.T) { num := 0 item := struct { - T time.Time `json:",string"` - M map[string]string `json:",string"` - S []string `json:",string"` - A [1]string `json:",string"` - I any `json:",string"` - P *int `json:",string"` + T time.Time `json:",string"` //nolint:staticcheck // It's intentionally wrong. + M map[string]string `json:",string"` //nolint:staticcheck // It's intentionally wrong. + S []string `json:",string"` //nolint:staticcheck // It's intentionally wrong. + A [1]string `json:",string"` //nolint:staticcheck // It's intentionally wrong. + I any `json:",string"` //nolint:staticcheck // It's intentionally wrong. + P *int `json:",string"` //nolint:staticcheck // It's intentionally wrong. }{M: make(map[string]string), S: make([]string, 0), I: num, P: &num} data, err := Marshal(item) diff --git a/encode_test.go b/encode_test.go index 1bcec6c..f540dcf 100644 --- a/encode_test.go +++ b/encode_test.go @@ -24,7 +24,7 @@ type Optionals struct { Ir int `json:"omitempty"` // actually named omitempty, not an option Io int `json:"io,omitempty"` - Slr []string `json:"slr,random"` + Slr []string `json:"slr,random"` //nolint:staticcheck // It's intentionally wrong. Slo []string `json:"slo,omitempty"` Mr map[string]any `json:"mr"` diff --git a/tagkey_test.go b/tagkey_test.go index 9678097..cfd7dc2 100644 --- a/tagkey_test.go +++ b/tagkey_test.go @@ -57,11 +57,11 @@ type misnamedTag struct { } type badFormatTag struct { - Y string `:"BadFormat"` + Y string `:"BadFormat"` //nolint:govet // It's intentionally wrong. } type badCodeTag struct { - Z string `json:" !\"#&'()*+,."` + Z string `json:" !\"#&'()*+,."` //nolint:staticcheck // It's intentionally wrong. } type spaceTag struct { From f509a2b797b4891c45978e448781bbf02b276b9d Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 9 Aug 2024 21:51:35 +0300 Subject: [PATCH 35/67] golangci: add linter config (taken from NeoGo) Signed-off-by: Roman Khimov --- .golangci.yml | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 .golangci.yml diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..3597c70 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,73 @@ +# This file contains all available configuration options +# with their default values. + +# options for analysis running +run: + # timeout for analysis, e.g. 30s, 5m, default is 1m + timeout: 5m + + # include test files or not, default is true + tests: true + +# output configuration options +output: + # colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number" + formats: + - format: tab + +# all available settings of specific linters +linters-settings: + exhaustive: + # indicates that switch statements are to be considered exhaustive if a + # 'default' case is present, even if all enum members aren't listed in the + # switch + default-signifies-exhaustive: true + +linters: + enable: + # mandatory linters + - govet + - revive + + # some default golangci-lint linters + - errcheck + - gosimple + - godot + - ineffassign + - staticcheck + - typecheck + - unused + + # extra linters + # - exhaustive + # - goconst + # - goerr113 + # - gomnd + # - nonamedreturns + # - unparam + - bidichk + - bodyclose + - contextcheck + - decorder + - durationcheck + - errorlint + - exportloopref + - gofmt + - misspell + - predeclared + - reassign + - whitespace + - goimports + disable-all: true + fast: false + +issues: + include: + - EXC0002 # should have a comment + - EXC0003 # test/Test ... consider calling this + - EXC0004 # govet + - EXC0005 # C-style breaks + exclude-rules: + - linters: + - revive + text: "unused-parameter" From ed98fd478d76db1000f0b816ae956eac714c30ba Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 9 Aug 2024 21:52:44 +0300 Subject: [PATCH 36/67] go.mod: upgrade to Go 1.21 min Fixes #6. Follows version policy change in https://github.com/nspcc-dev/.github/issues/30. Signed-off-by: Roman Khimov --- .github/workflows/run_tests.yml | 8 +------- go.mod | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 2df59a6..736e55c 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -44,20 +44,14 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - go_versions: [ '1.20', '1.21', '1.22' ] + go_versions: [ '1.21', '1.22' ] os: [ubuntu-22.04, windows-2022, macos-12, macos-14] exclude: # Only latest Go version for Windows and MacOS. - - os: windows-2022 - go_versions: '1.20' - os: windows-2022 go_versions: '1.21' - - os: macos-12 - go_versions: '1.20' - os: macos-12 go_versions: '1.21' - - os: macos-14 - go_versions: '1.20' - os: macos-14 go_versions: '1.21' # Exclude latest Go version for Ubuntu as Coverage uses it. diff --git a/go.mod b/go.mod index 3bc08d1..80d54ee 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ module github.com/nspcc-dev/go-ordered-json -go 1.20 +go 1.21 From 72c198a3a3b69bad8b85572df783182e92006430 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 9 Aug 2024 23:27:19 +0300 Subject: [PATCH 37/67] *: fix godot warnings Signed-off-by: Roman Khimov --- decode_test.go | 14 +++++++------- encode_test.go | 4 ++-- stream_test.go | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/decode_test.go b/decode_test.go index 530e653..992650c 100644 --- a/decode_test.go +++ b/decode_test.go @@ -42,7 +42,7 @@ type VOuter struct { } // ifaceNumAsFloat64/ifaceNumAsNumber are used to test unmarshaling with and -// without UseNumber +// without UseNumber. var ifaceNumAsFloat64 = map[string]any{ "k1": float64(1), "k2": "s", @@ -82,7 +82,7 @@ type unmarshalerText struct { A, B string } -// needed for re-marshaling tests +// MarshalText is needed for re-marshaling tests. func (u unmarshalerText) MarshalText() ([]byte, error) { return []byte(u.A + ":" + u.B), nil } @@ -1028,7 +1028,7 @@ var numberTests = []struct { {in: "1e1000", intErr: "strconv.ParseInt: parsing \"1e1000\": invalid syntax", floatErr: "strconv.ParseFloat: parsing \"1e1000\": value out of range"}, } -// Independent of Decode, basic coverage of the accessors in Number +// Independent of Decode, basic coverage of the accessors in Number. func TestNumberAccessors(t *testing.T) { for _, tt := range numberTests { n := Number(tt.in) @@ -1483,7 +1483,7 @@ func TestRefUnmarshal(t *testing.T) { } // Test that the empty string doesn't panic decoding when ,string is specified -// Issue 3450 +// Issue 3450. func TestEmptyString(t *testing.T) { type T2 struct { Number1 int `json:",string"` @@ -1640,7 +1640,7 @@ type NullTestStrings struct { } // JSON null values should be ignored for primitives and string values instead of resulting in an error. -// Issue 2540 +// Issue 2540. func TestUnmarshalNulls(t *testing.T) { // Unmarshal docs: // The JSON null value unmarshals into an interface, map, pointer, or slice @@ -1886,7 +1886,7 @@ func TestUnmarshalSyntax(t *testing.T) { } // Test handling of unexported fields that should be ignored. -// Issue 4660 +// Issue 4660. type unexportedFields struct { Name string m map[string]any `json:"-"` //nolint:unused // Not really used, but important for test. @@ -1936,7 +1936,7 @@ func TestUnmarshalJSONLiteralError(t *testing.T) { // Test that extra object elements in an array do not result in a // "data changing underfoot" error. -// Issue 3717 +// Issue 3717. func TestSkipArrayObjects(t *testing.T) { json := `[{}]` var dest [0]any diff --git a/encode_test.go b/encode_test.go index f540dcf..7096f45 100644 --- a/encode_test.go +++ b/encode_test.go @@ -547,7 +547,7 @@ func TestHTMLEscape(t *testing.T) { } } -// golang.org/issue/8582 +// golang.org/issue/8582. func TestEncodePointerString(t *testing.T) { type stringPointer struct { N *int64 `json:"n,string"` @@ -625,7 +625,7 @@ func tenc(format string, a ...any) ([]byte, error) { return buf.Bytes(), nil } -// Issue 13783 +// Issue 13783. func TestEncodeBytekind(t *testing.T) { testdata := []struct { data any diff --git a/stream_test.go b/stream_test.go index 2f7b0a3..c20972e 100644 --- a/stream_test.go +++ b/stream_test.go @@ -395,7 +395,7 @@ func TestDecodeInStream(t *testing.T) { } -// Test from golang.org/issue/11893 +// Test from golang.org/issue/11893. func TestHTTPDecoding(t *testing.T) { const raw = `{ "foo": "bar" }` From 512a921ce623f53f13fb99aa028f87d3ff2521e2 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 9 Aug 2024 23:27:31 +0300 Subject: [PATCH 38/67] encode: use slices.SortFunc where appropriate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit goos: linux goarch: amd64 pkg: github.com/nspcc-dev/go-ordered-json cpu: AMD Ryzen 7 PRO 7840U w/ Radeon 780M Graphics │ slices.pre │ slices.post │ │ sec/op │ sec/op vs base │ CodeEncoder-16 675.7µ ± 1% 670.7µ ± 2% ~ (p=0.280 n=10) CodeMarshal-16 862.3µ ± 1% 879.2µ ± 1% +1.96% (p=0.002 n=10) CodeDecoder-16 3.720m ± 2% 3.936m ± 1% +5.82% (p=0.000 n=10) DecoderStream-16 86.03n ± 1% 89.39n ± 1% +3.91% (p=0.000 n=10) CodeUnmarshal-16 3.809m ± 1% 4.340m ± 3% +13.95% (p=0.000 n=10) CodeUnmarshalReuse-16 4.022m ± 10% 3.918m ± 2% ~ (p=0.631 n=10) UnmarshalString-16 46.14n ± 1% 46.31n ± 3% ~ (p=0.971 n=10) UnmarshalFloat64-16 42.96n ± 2% 42.90n ± 1% ~ (p=0.424 n=10) UnmarshalInt64-16 36.69n ± 2% 36.55n ± 2% ~ (p=0.425 n=10) Issue10335-16 54.79n ± 2% 54.00n ± 1% -1.44% (p=0.002 n=10) Unmapped-16 142.1n ± 1% 137.1n ± 2% -3.55% (p=0.000 n=10) NumberIsValid-16 8.996n ± 2% 8.923n ± 2% ~ (p=0.280 n=10) NumberIsValidRegexp-16 194.1n ± 1% 191.3n ± 1% -1.42% (p=0.008 n=10) SkipValue-16 7.786m ± 2% 7.649m ± 5% ~ (p=0.052 n=10) EncoderEncode-16 20.07n ± 1% 21.11n ± 7% ~ (p=0.165 n=10) geomean 3.770µ 3.813µ +1.14% │ slices.pre │ slices.post │ │ B/s │ B/s vs base │ CodeEncoder-16 2.675Gi ± 1% 2.694Gi ± 2% ~ (p=0.280 n=10) CodeMarshal-16 2.096Gi ± 1% 2.056Gi ± 1% -1.92% (p=0.002 n=10) CodeDecoder-16 497.5Mi ± 2% 470.1Mi ± 1% -5.50% (p=0.000 n=10) CodeUnmarshal-16 485.9Mi ± 1% 426.4Mi ± 3% -12.24% (p=0.000 n=10) SkipValue-16 266.0Mi ± 3% 271.8Mi ± 1% +2.19% (p=0.019 n=10) geomean 823.1Mi 794.4Mi -3.49% │ slices.pre │ slices.post │ │ B/op │ B/op vs base │ CodeEncoder-16 3.500 ± 71% 5.000 ± 60% ~ (p=0.447 n=10) CodeMarshal-16 4.000Mi ± 0% 4.000Mi ± 0% ~ (p=0.191 n=10) CodeDecoder-16 1.911Mi ± 2% 1.932Mi ± 2% ~ (p=0.101 n=10) DecoderStream-16 8.000 ± 0% 8.000 ± 0% ~ (p=1.000 n=10) ¹ CodeUnmarshal-16 2.903Mi ± 0% 2.903Mi ± 0% ~ (p=0.092 n=10) CodeUnmarshalReuse-16 1.614Mi ± 0% 1.614Mi ± 1% ~ (p=0.853 n=10) UnmarshalString-16 304.0 ± 0% 304.0 ± 0% ~ (p=1.000 n=10) ¹ UnmarshalFloat64-16 292.0 ± 0% 292.0 ± 0% ~ (p=1.000 n=10) ¹ UnmarshalInt64-16 288.0 ± 0% 288.0 ± 0% ~ (p=1.000 n=10) ¹ Issue10335-16 312.0 ± 0% 312.0 ± 0% ~ (p=1.000 n=10) ¹ Unmapped-16 344.0 ± 0% 344.0 ± 0% ~ (p=1.000 n=10) ¹ NumberIsValid-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ NumberIsValidRegexp-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ SkipValue-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) EncoderEncode-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ geomean ² +2.48% ² ¹ all samples are equal ² summaries must be >0 to compute geomean │ slices.pre │ slices.post │ │ allocs/op │ allocs/op vs base │ CodeEncoder-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ CodeMarshal-16 17.00 ± 0% 17.00 ± 0% ~ (p=1.000 n=10) ¹ CodeDecoder-16 77.09k ± 0% 77.14k ± 0% ~ (p=0.085 n=10) DecoderStream-16 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹ CodeUnmarshal-16 92.66k ± 0% 92.66k ± 0% ~ (p=1.000 n=10) ¹ CodeUnmarshalReuse-16 77.19k ± 0% 77.19k ± 0% ~ (p=0.868 n=10) UnmarshalString-16 2.000 ± 0% 2.000 ± 0% ~ (p=1.000 n=10) ¹ UnmarshalFloat64-16 2.000 ± 0% 2.000 ± 0% ~ (p=1.000 n=10) ¹ UnmarshalInt64-16 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹ Issue10335-16 3.000 ± 0% 3.000 ± 0% ~ (p=1.000 n=10) ¹ Unmapped-16 4.000 ± 0% 4.000 ± 0% ~ (p=1.000 n=10) ¹ NumberIsValid-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ NumberIsValidRegexp-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ SkipValue-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ EncoderEncode-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ geomean ² +0.00% ² ¹ all samples are equal ² summaries must be >0 to compute geomean Signed-off-by: Roman Khimov --- encode.go | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/encode.go b/encode.go index f971e42..513de3a 100644 --- a/encode.go +++ b/encode.go @@ -18,6 +18,7 @@ import ( "math" "reflect" "runtime" + "slices" "sort" "strconv" "strings" @@ -1110,23 +1111,16 @@ func fillField(f field) field { return f } -// byIndex sorts field by index sequence. -type byIndex []field - -func (x byIndex) Len() int { return len(x) } - -func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] } - -func (x byIndex) Less(i, j int) bool { - for k, xik := range x[i].index { - if k >= len(x[j].index) { - return false +func cmpFieldsByIndex(a, b field) int { + for k, ak := range a.index { + if k >= len(b.index) { + return 1 } - if xik != x[j].index[k] { - return xik < x[j].index[k] + if ak != b.index[k] { + return ak - b.index[k] } } - return len(x[i].index) < len(x[j].index) + return len(a.index) - len(b.index) } // typeFields returns a list of fields that JSON should recognize for the given type. @@ -1242,7 +1236,7 @@ func typeFields(t reflect.Type) []field { if x[i].tag != x[j].tag { return x[i].tag } - return byIndex(x).Less(i, j) + return cmpFieldsByIndex(x[i], x[j]) < 0 }) // Delete all fields that are hidden by the Go rules for embedded fields, @@ -1274,7 +1268,7 @@ func typeFields(t reflect.Type) []field { } fields = out - sort.Sort(byIndex(fields)) + slices.SortFunc(fields, cmpFieldsByIndex) return fields } From 7bc9255dd022229f6b46ec73308cfc3ecf9d1cea Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 9 Aug 2024 23:29:04 +0300 Subject: [PATCH 39/67] decode: apply gofmt -s Signed-off-by: Roman Khimov --- decode_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/decode_test.go b/decode_test.go index 992650c..64d53c6 100644 --- a/decode_test.go +++ b/decode_test.go @@ -142,7 +142,7 @@ var ( umstructXY = ustructText{unmarshalerText{"x", "y"}} ummapType = map[unmarshalerText]bool{} - ummapXY = map[unmarshalerText]bool{unmarshalerText{"x", "y"}: true} + ummapXY = map[unmarshalerText]bool{{"x", "y"}: true} ) // Test data structures for anonymous fields. From 3db0a709c3ac662a3bf572e749b82ae42599e16f Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 9 Aug 2024 23:30:44 +0300 Subject: [PATCH 40/67] *: fix whitespace errors reported by linter Signed-off-by: Roman Khimov --- example_test.go | 1 - stream_test.go | 4 ---- 2 files changed, 5 deletions(-) diff --git a/example_test.go b/example_test.go index d85f73b..ec33c3a 100644 --- a/example_test.go +++ b/example_test.go @@ -172,7 +172,6 @@ func ExampleDecoder_Decode_stream() { // Sam: Go fmt who? // Ed: Go fmt yourself! // json.Delim: ] - } // This example uses RawMessage to delay parsing part of a JSON message. diff --git a/stream_test.go b/stream_test.go index c20972e..68c6b94 100644 --- a/stream_test.go +++ b/stream_test.go @@ -359,12 +359,9 @@ var tokenStreamCases []tokenStreamCase = []tokenStreamCase{ } func TestDecodeInStream(t *testing.T) { - for ci, tcase := range tokenStreamCases { - dec := NewDecoder(strings.NewReader(tcase.json)) for i, etk := range tcase.expTokens { - var tk any var err error @@ -392,7 +389,6 @@ func TestDecodeInStream(t *testing.T) { } } } - } // Test from golang.org/issue/11893. From 30833306b5c3bf7ad941febcfb04e27e950ec20c Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 9 Aug 2024 23:32:46 +0300 Subject: [PATCH 41/67] stream: suppress revive linter errors var-naming: struct field Id should be ID (revive) Signed-off-by: Roman Khimov --- stream_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stream_test.go b/stream_test.go index 68c6b94..8cee2ca 100644 --- a/stream_test.go +++ b/stream_test.go @@ -198,7 +198,7 @@ func TestRawMessage(t *testing.T) { // TODO(rsc): Should not need the * in *RawMessage var data struct { X float64 - Id *RawMessage + Id *RawMessage //nolint:revive // It's "Id" in JSON and it's a test. Y float32 } const raw = `["\u0056",null]` @@ -223,7 +223,7 @@ func TestNullRawMessage(t *testing.T) { // TODO(rsc): Should not need the * in *RawMessage var data struct { X float64 - Id *RawMessage + Id *RawMessage //nolint:revive // It's "Id" in JSON and it's a test. Y float32 } data.Id = new(RawMessage) From 712357f2aa1624107618492ea531364e53bfa683 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 9 Aug 2024 23:33:45 +0300 Subject: [PATCH 42/67] decode: drop redundant else branch decode.go:1107:9: indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (revive) Signed-off-by: Roman Khimov --- decode.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/decode.go b/decode.go index efdfc6b..dc81688 100644 --- a/decode.go +++ b/decode.go @@ -1104,9 +1104,8 @@ func (d *decodeState) objectInterface(forceOrderedObject bool) any { if d.useOrderedObject || forceOrderedObject { return v - } else { - return m } + return m } // literalInterface is like literal but returns an interface value. From 8ca743185cffb9b338e832badf99b48e0a02402e Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 9 Aug 2024 23:34:58 +0300 Subject: [PATCH 43/67] *: simplify var declarations stream_test.go:297:22: var-declaration: should omit type []tokenStreamCase from declaration of var tokenStreamCases; it will be inferred from the right-hand side (revive) encode.go:717:9: var-declaration: should omit type OrderedObject from declaration of var ov; it will be inferred from the right-hand side (revive) Signed-off-by: Roman Khimov --- encode.go | 2 +- stream_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/encode.go b/encode.go index 513de3a..fca4ba9 100644 --- a/encode.go +++ b/encode.go @@ -714,7 +714,7 @@ func orderedObjectEncoder(e *encodeState, v reflect.Value, opts encOpts) { return } e.WriteByte('{') - var ov OrderedObject = v.Interface().(OrderedObject) + var ov = v.Interface().(OrderedObject) for i, o := range ov { if i > 0 { e.WriteByte(',') diff --git a/stream_test.go b/stream_test.go index 8cee2ca..d50fe81 100644 --- a/stream_test.go +++ b/stream_test.go @@ -294,7 +294,7 @@ type decodeThis struct { v any } -var tokenStreamCases []tokenStreamCase = []tokenStreamCase{ +var tokenStreamCases = []tokenStreamCase{ // streaming token cases {json: `10`, expTokens: []any{float64(10)}}, {json: ` [10] `, expTokens: []any{ From bb5ef01ba5c2bcf0cd64221b6fa6407c2379e5bb Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 9 Aug 2024 23:35:31 +0300 Subject: [PATCH 44/67] example: simplify increment example_marshaling_test.go:62:3: increment-decrement: should replace census[animal] += 1 with census[animal]++ (revive) Signed-off-by: Roman Khimov --- example_marshaling_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example_marshaling_test.go b/example_marshaling_test.go index 7f15c74..0b1125b 100644 --- a/example_marshaling_test.go +++ b/example_marshaling_test.go @@ -59,7 +59,7 @@ func Example_customMarshalJSON() { census := make(map[Animal]int) for _, animal := range zoo { - census[animal] += 1 + census[animal]++ } fmt.Printf("Zoo Census:\n* Gophers: %d\n* Zebras: %d\n* Unknown: %d\n", From 9a3758772aec4a364e9a48fa58dbf3e34849e939 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 9 Aug 2024 23:43:22 +0300 Subject: [PATCH 45/67] test: suppress errorlint in cases where it doesn't make sense Signed-off-by: Roman Khimov --- decode_test.go | 4 ++-- encode_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/decode_test.go b/decode_test.go index 64d53c6..2e5ef37 100644 --- a/decode_test.go +++ b/decode_test.go @@ -1857,7 +1857,7 @@ var decodeTypeErrorTests = []struct { func TestUnmarshalTypeError(t *testing.T) { for _, item := range decodeTypeErrorTests { err := Unmarshal([]byte(item.src), item.dest) - if _, ok := err.(*UnmarshalTypeError); !ok { + if _, ok := err.(*UnmarshalTypeError); !ok { //nolint:errorlint // It must match exactly, it's a test. t.Errorf("expected type error for Unmarshal(%q, type %T): got %T", item.src, item.dest, err) } @@ -1879,7 +1879,7 @@ func TestUnmarshalSyntax(t *testing.T) { var x any for _, src := range unmarshalSyntaxTests { err := Unmarshal([]byte(src), &x) - if _, ok := err.(*SyntaxError); !ok { + if _, ok := err.(*SyntaxError); !ok { //nolint:errorlint // It must match exactly, it's a test. t.Errorf("expected syntax error for Unmarshal(%q): got %T", src, err) } } diff --git a/encode_test.go b/encode_test.go index 7096f45..fb90013 100644 --- a/encode_test.go +++ b/encode_test.go @@ -140,7 +140,7 @@ var unsupportedValues = []any{ func TestUnsupportedValues(t *testing.T) { for _, v := range unsupportedValues { if _, err := Marshal(v); err != nil { - if _, ok := err.(*UnsupportedValueError); !ok { + if _, ok := err.(*UnsupportedValueError); !ok { //nolint:errorlint // It must match exactly, it's a test. t.Errorf("for %v, got %T want UnsupportedValueError", v, err) } } else { From a3ffb79ce2f61d0d4b234f66c2d5de249a238115 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 9 Aug 2024 23:43:44 +0300 Subject: [PATCH 46/67] *: fix errorlint warnings decode.go:311:17: type switch on error will fail on wrapped errors. Use errors.As to check for specific errors (errorlint) stream.go:506:14: type assertion on error will fail on wrapped errors. Use errors.As to check for specific errors (errorlint) stream_test.go:426:5: comparing with != will fail on wrapped errors. Use errors.Is to check for a specific error (errorlint) Signed-off-by: Roman Khimov --- decode.go | 10 +++++----- stream.go | 3 ++- stream_test.go | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/decode.go b/decode.go index dc81688..3a2f6bd 100644 --- a/decode.go +++ b/decode.go @@ -308,11 +308,11 @@ func (d *decodeState) saveError(err error) { // addErrorContext returns a new error enhanced with information from d.errorContext. func (d *decodeState) addErrorContext(err error) error { if d.errorContext.Struct != "" || d.errorContext.Field != "" { - switch err := err.(type) { - case *UnmarshalTypeError: - err.Struct = d.errorContext.Struct - err.Field = d.errorContext.Field - return err + var e *UnmarshalTypeError + if errors.As(err, &e) { + e.Struct = d.errorContext.Struct + e.Field = d.errorContext.Field + return e } } return err diff --git a/stream.go b/stream.go index 5fe2753..3c69a14 100644 --- a/stream.go +++ b/stream.go @@ -503,7 +503,8 @@ func (dec *Decoder) Token() (Token, error) { } func clearOffset(err error) { - if s, ok := err.(*SyntaxError); ok { + var s *SyntaxError + if errors.As(err, &s) { s.Offset = 0 } } diff --git a/stream_test.go b/stream_test.go index d50fe81..2d90968 100644 --- a/stream_test.go +++ b/stream_test.go @@ -423,7 +423,7 @@ func TestHTTPDecoding(t *testing.T) { // make sure we get the EOF the second time err = d.Decode(&foo) - if err != io.EOF { + if !errors.Is(err, io.EOF) { t.Errorf("err = %v; want io.EOF", err) } } From 180b9a5977758409dc93d2574bab2e53b5f5d6a4 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Wed, 28 Aug 2024 22:50:22 +0300 Subject: [PATCH 47/67] go.mod: upgrade to Go 1.22 min And use 1.23 for tests now. Fixes #10. Signed-off-by: Roman Khimov --- .github/workflows/run_tests.yml | 12 ++++++------ go.mod | 3 +-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 736e55c..aab287e 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -27,7 +27,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: 1.22 + go-version: 1.23 - name: Write coverage profile run: go test -v ./... -coverprofile=./coverage.txt -covermode=atomic @@ -44,19 +44,19 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - go_versions: [ '1.21', '1.22' ] + go_versions: [ '1.22', '1.23' ] os: [ubuntu-22.04, windows-2022, macos-12, macos-14] exclude: # Only latest Go version for Windows and MacOS. - os: windows-2022 - go_versions: '1.21' + go_versions: '1.22' - os: macos-12 - go_versions: '1.21' + go_versions: '1.22' - os: macos-14 - go_versions: '1.21' + go_versions: '1.22' # Exclude latest Go version for Ubuntu as Coverage uses it. - os: ubuntu-20.04 - go_versions: '1.22' + go_versions: '1.23' fail-fast: false steps: - uses: actions/checkout@v4 diff --git a/go.mod b/go.mod index 80d54ee..0f73cba 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,3 @@ module github.com/nspcc-dev/go-ordered-json -go 1.21 - +go 1.22 From af4b489e49c514f2de8e57153f24c4b3fd76e1d5 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Wed, 28 Aug 2024 22:51:11 +0300 Subject: [PATCH 48/67] *: use Go 1.22+ ranging over integers Signed-off-by: Roman Khimov --- bench_test.go | 2 +- encode.go | 4 ++-- indent.go | 2 +- number_test.go | 4 ++-- scanner_test.go | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bench_test.go b/bench_test.go index f5a3613..8d51375 100644 --- a/bench_test.go +++ b/bench_test.go @@ -144,7 +144,7 @@ func BenchmarkDecoderStream(b *testing.B) { } ones := strings.Repeat(" 1\n", 300000) + "\n\n\n" b.StartTimer() - for i := 0; i < b.N; i++ { + for i := range b.N { if i%300000 == 0 { buf.WriteString(ones) } diff --git a/encode.go b/encode.go index fca4ba9..c668319 100644 --- a/encode.go +++ b/encode.go @@ -783,7 +783,7 @@ type arrayEncoder struct { func (ae *arrayEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { e.WriteByte('[') n := v.Len() - for i := 0; i < n; i++ { + for i := range n { if i > 0 { e.WriteByte(',') } @@ -1152,7 +1152,7 @@ func typeFields(t reflect.Type) []field { visited[f.typ] = true // Scan f.typ for fields to include. - for i := 0; i < f.typ.NumField(); i++ { + for i := range f.typ.NumField() { sf := f.typ.Field(i) if sf.PkgPath != "" && (!sf.Anonymous || sf.Type.Kind() != reflect.Struct) { // unexported continue diff --git a/indent.go b/indent.go index fba1954..aea0a4b 100644 --- a/indent.go +++ b/indent.go @@ -60,7 +60,7 @@ func compact(dst *bytes.Buffer, src []byte, escape bool) error { func newline(dst *bytes.Buffer, prefix, indent string, depth int) { dst.WriteByte('\n') dst.WriteString(prefix) - for i := 0; i < depth; i++ { + for range depth { dst.WriteString(indent) } } diff --git a/number_test.go b/number_test.go index 4b86999..1d45b8e 100644 --- a/number_test.go +++ b/number_test.go @@ -119,7 +119,7 @@ func TestNumberIsValid(t *testing.T) { func BenchmarkNumberIsValid(b *testing.B) { s := "-61657.61667E+61673" - for i := 0; i < b.N; i++ { + for range b.N { isValidNumber(s) } } @@ -127,7 +127,7 @@ func BenchmarkNumberIsValid(b *testing.B) { func BenchmarkNumberIsValidRegexp(b *testing.B) { var jsonNumberRegexp = regexp.MustCompile(`^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$`) s := "-61657.61667E+61673" - for i := 0; i < b.N; i++ { + for range b.N { jsonNumberRegexp.MatchString(s) } } diff --git a/scanner_test.go b/scanner_test.go index 50d43ca..c472c07 100644 --- a/scanner_test.go +++ b/scanner_test.go @@ -231,7 +231,7 @@ var benchScan scanner func BenchmarkSkipValue(b *testing.B) { initBig() b.ResetTimer() - for i := 0; i < b.N; i++ { + for range b.N { _, _, _ = nextValue(jsonBig, &benchScan) } b.SetBytes(int64(len(jsonBig))) @@ -330,7 +330,7 @@ func genMap(n int) map[string]any { f = 1 } x := make(map[string]any) - for i := 0; i < f; i++ { + for i := range f { x[genString(10)] = genValue(((i+1)*n)/f - (i*n)/f) } return x From 1cbefe4c40675253cdb388d78d73b666198e9dfb Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Wed, 28 Aug 2024 22:56:24 +0300 Subject: [PATCH 49/67] scanner: switch test to math/rand/v2 Signed-off-by: Roman Khimov --- scanner_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scanner_test.go b/scanner_test.go index c472c07..b540318 100644 --- a/scanner_test.go +++ b/scanner_test.go @@ -7,7 +7,7 @@ package json import ( "bytes" "math" - "math/rand" + "math/rand/v2" "reflect" "testing" ) @@ -275,16 +275,16 @@ func initBig() { func genValue(n int) any { if n > 1 { - switch rand.Intn(2) { + switch rand.IntN(2) { case 0: return genArray(n) case 1: return genMap(n) } } - switch rand.Intn(3) { + switch rand.IntN(3) { case 0: - return rand.Intn(2) == 0 + return rand.IntN(2) == 0 case 1: return rand.NormFloat64() case 2: From 2b0701abb86d3e56e39a31f6af7f75f09879b9f7 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Wed, 28 Aug 2024 23:08:57 +0300 Subject: [PATCH 50/67] golangci: don't use deprecated linter level=warning msg="The linter 'exportloopref' is deprecated (since v1.60.2) due to: Since Go1.22 (loopvar) this linter is no longer relevant. Replaced by copyloopvar." Signed-off-by: Roman Khimov --- .golangci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.golangci.yml b/.golangci.yml index 3597c70..5169c5a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -51,7 +51,7 @@ linters: - decorder - durationcheck - errorlint - - exportloopref + - copyloopvar - gofmt - misspell - predeclared From 6fb29531ac57e85a4b9979e8cce9a9a63d9f5264 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Wed, 28 Aug 2024 23:10:13 +0300 Subject: [PATCH 51/67] *: use PointerTo instead of PtrTo Error: SA1019: reflect.PtrTo has been deprecated since Go 1.22 and an alternative has been available since Go 1.18: Superseded by [PointerTo]. (staticcheck) These methods are exactly the same. Signed-off-by: Roman Khimov --- decode.go | 4 ++-- encode.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/decode.go b/decode.go index 3a2f6bd..3b4b375 100644 --- a/decode.go +++ b/decode.go @@ -628,7 +628,7 @@ func (d *decodeState) object(v reflect.Value) { reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: default: - if !reflect.PtrTo(t.Key()).Implements(textUnmarshalerType) { + if !reflect.PointerTo(t.Key()).Implements(textUnmarshalerType) { d.saveError(&UnmarshalTypeError{Value: "object", Type: v.Type(), Offset: int64(d.off)}) d.off-- d.next() // skip over { } in input @@ -740,7 +740,7 @@ func (d *decodeState) object(v reflect.Value) { switch { case kt.Kind() == reflect.String: kv = reflect.ValueOf(key).Convert(kt) - case reflect.PtrTo(kt).Implements(textUnmarshalerType): + case reflect.PointerTo(kt).Implements(textUnmarshalerType): kv = reflect.New(v.Type().Key()) d.literalStore(item, kv, true) kv = kv.Elem() diff --git a/encode.go b/encode.go index c668319..c921835 100644 --- a/encode.go +++ b/encode.go @@ -388,7 +388,7 @@ func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc { return marshalerEncoder } if t.Kind() != reflect.Ptr && allowAddr { - if reflect.PtrTo(t).Implements(marshalerType) { + if reflect.PointerTo(t).Implements(marshalerType) { return newCondAddrEncoder(addrMarshalerEncoder, newTypeEncoder(t, false)) } } @@ -397,7 +397,7 @@ func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc { return textMarshalerEncoder } if t.Kind() != reflect.Ptr && allowAddr { - if reflect.PtrTo(t).Implements(textMarshalerType) { + if reflect.PointerTo(t).Implements(textMarshalerType) { return newCondAddrEncoder(addrTextMarshalerEncoder, newTypeEncoder(t, false)) } } @@ -767,7 +767,7 @@ func (se *sliceEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { func newSliceEncoder(t reflect.Type) encoderFunc { // Byte slices get special treatment; arrays don't. if t.Elem().Kind() == reflect.Uint8 { - p := reflect.PtrTo(t.Elem()) + p := reflect.PointerTo(t.Elem()) if !p.Implements(marshalerType) && !p.Implements(textMarshalerType) { return encodeByteSlice } From 621e376df1849ce9e82052af9190c644ee2fbd6c Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 10 Sep 2024 15:26:05 +0300 Subject: [PATCH 52/67] workflows: switch to org-wide linter No Makefile here, so just a GH update. Signed-off-by: Roman Khimov --- .github/workflows/run_tests.yml | 14 +------ .golangci.yml | 73 --------------------------------- 2 files changed, 2 insertions(+), 85 deletions(-) delete mode 100644 .golangci.yml diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index aab287e..748cd23 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -72,18 +72,8 @@ jobs: run: go test -v -race ./... lint: - name: Lint - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version-file: 'go.mod' - - name: golangci-lint - uses: golangci/golangci-lint-action@v4 - with: - version: latest + name: Linter + uses: nspcc-dev/.github/.github/workflows/go-linter.yml@master codeql: name: CodeQL diff --git a/.golangci.yml b/.golangci.yml deleted file mode 100644 index 5169c5a..0000000 --- a/.golangci.yml +++ /dev/null @@ -1,73 +0,0 @@ -# This file contains all available configuration options -# with their default values. - -# options for analysis running -run: - # timeout for analysis, e.g. 30s, 5m, default is 1m - timeout: 5m - - # include test files or not, default is true - tests: true - -# output configuration options -output: - # colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number" - formats: - - format: tab - -# all available settings of specific linters -linters-settings: - exhaustive: - # indicates that switch statements are to be considered exhaustive if a - # 'default' case is present, even if all enum members aren't listed in the - # switch - default-signifies-exhaustive: true - -linters: - enable: - # mandatory linters - - govet - - revive - - # some default golangci-lint linters - - errcheck - - gosimple - - godot - - ineffassign - - staticcheck - - typecheck - - unused - - # extra linters - # - exhaustive - # - goconst - # - goerr113 - # - gomnd - # - nonamedreturns - # - unparam - - bidichk - - bodyclose - - contextcheck - - decorder - - durationcheck - - errorlint - - copyloopvar - - gofmt - - misspell - - predeclared - - reassign - - whitespace - - goimports - disable-all: true - fast: false - -issues: - include: - - EXC0002 # should have a comment - - EXC0003 # test/Test ... consider calling this - - EXC0004 # govet - - EXC0005 # C-style breaks - exclude-rules: - - linters: - - revive - text: "unused-parameter" From e0222902cd197aa2a1c8197468c9503a7c311b61 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 5 Nov 2024 12:55:12 +0300 Subject: [PATCH 53/67] workflows: drop macos-12 runner support Support for macos-12 will be deprecated 10/7/24 and the image will be fully unsupported by 12/3/24. Ref. actions/runner-images#10721. Get rid of the build warning: ``` A brownout will take place on November 4, 14:00 UTC - November 5, 00:00 UTC to raise awareness of the upcoming macOS-12 environment removal. ``` Signed-off-by: Anna Shaleva --- .github/workflows/run_tests.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 748cd23..16ce263 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -45,13 +45,11 @@ jobs: strategy: matrix: go_versions: [ '1.22', '1.23' ] - os: [ubuntu-22.04, windows-2022, macos-12, macos-14] + os: [ubuntu-22.04, windows-2022, macos-14] exclude: # Only latest Go version for Windows and MacOS. - os: windows-2022 go_versions: '1.22' - - os: macos-12 - go_versions: '1.22' - os: macos-14 go_versions: '1.22' # Exclude latest Go version for Ubuntu as Coverage uses it. From cff619b3bce91b90b77d6b0d63e6d9055c681511 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Wed, 26 Feb 2025 16:32:33 +0300 Subject: [PATCH 54/67] workflows: use ubuntu-latest for testing Signed-off-by: Roman Khimov --- .github/workflows/run_tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 16ce263..a8012e8 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -15,7 +15,7 @@ env: jobs: test_cover: name: Coverage - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest env: CGO_ENABLED: 0 @@ -45,7 +45,7 @@ jobs: strategy: matrix: go_versions: [ '1.22', '1.23' ] - os: [ubuntu-22.04, windows-2022, macos-14] + os: [ubuntu-latest, windows-2022, macos-14] exclude: # Only latest Go version for Windows and MacOS. - os: windows-2022 @@ -53,7 +53,7 @@ jobs: - os: macos-14 go_versions: '1.22' # Exclude latest Go version for Ubuntu as Coverage uses it. - - os: ubuntu-20.04 + - os: ubuntu-latest go_versions: '1.23' fail-fast: false steps: From ae223c33d887515258fc578251d1a62dc9a2743b Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Wed, 26 Feb 2025 16:37:00 +0300 Subject: [PATCH 55/67] workflows: switch to CodeQL v3 v2 is obsolete and no longer supported. Signed-off-by: Roman Khimov --- .github/workflows/run_tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index a8012e8..c6789f1 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -91,7 +91,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -102,7 +102,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -116,4 +116,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 From 23a4100eb00832909cf848fbb6b6c6baec3f18e0 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Wed, 26 Feb 2025 16:37:48 +0300 Subject: [PATCH 56/67] workflows: switch to Go 1.23/1.24 for testing 1.22 is obsolete. Signed-off-by: Roman Khimov --- .github/workflows/run_tests.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index c6789f1..20df7ea 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -27,7 +27,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: 1.23 + go-version: 1.24 - name: Write coverage profile run: go test -v ./... -coverprofile=./coverage.txt -covermode=atomic @@ -44,17 +44,17 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - go_versions: [ '1.22', '1.23' ] + go_versions: [ '1.23', '1.24' ] os: [ubuntu-latest, windows-2022, macos-14] exclude: # Only latest Go version for Windows and MacOS. - os: windows-2022 - go_versions: '1.22' + go_versions: '1.23' - os: macos-14 - go_versions: '1.22' + go_versions: '1.23' # Exclude latest Go version for Ubuntu as Coverage uses it. - os: ubuntu-latest - go_versions: '1.23' + go_versions: '1.24' fail-fast: false steps: - uses: actions/checkout@v4 From c4cb39da926294b2a55f985dd04f449b06541515 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 9 Sep 2025 22:26:12 +0300 Subject: [PATCH 57/67] go.mod: upgrade to Go 1.24, fix #18 Signed-off-by: Roman Khimov --- .github/workflows/run_tests.yml | 10 +++++----- go.mod | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 20df7ea..f9be4fc 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -27,7 +27,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: 1.24 + go-version: 1.25 - name: Write coverage profile run: go test -v ./... -coverprofile=./coverage.txt -covermode=atomic @@ -44,17 +44,17 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - go_versions: [ '1.23', '1.24' ] + go_versions: [ '1.24', '1.25' ] os: [ubuntu-latest, windows-2022, macos-14] exclude: # Only latest Go version for Windows and MacOS. - os: windows-2022 - go_versions: '1.23' + go_versions: '1.24' - os: macos-14 - go_versions: '1.23' + go_versions: '1.24' # Exclude latest Go version for Ubuntu as Coverage uses it. - os: ubuntu-latest - go_versions: '1.24' + go_versions: '1.25' fail-fast: false steps: - uses: actions/checkout@v4 diff --git a/go.mod b/go.mod index 0f73cba..627d352 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/nspcc-dev/go-ordered-json -go 1.22 +go 1.24 From 9bbd4ebfc9deb78726afa076d87e909f3095f122 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 9 Sep 2025 22:27:55 +0300 Subject: [PATCH 58/67] *: use Go 1.24 b.Loop() in benchmarks Signed-off-by: Roman Khimov --- number_test.go | 4 ++-- scanner_test.go | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/number_test.go b/number_test.go index 1d45b8e..5d57bb0 100644 --- a/number_test.go +++ b/number_test.go @@ -119,7 +119,7 @@ func TestNumberIsValid(t *testing.T) { func BenchmarkNumberIsValid(b *testing.B) { s := "-61657.61667E+61673" - for range b.N { + for b.Loop() { isValidNumber(s) } } @@ -127,7 +127,7 @@ func BenchmarkNumberIsValid(b *testing.B) { func BenchmarkNumberIsValidRegexp(b *testing.B) { var jsonNumberRegexp = regexp.MustCompile(`^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$`) s := "-61657.61667E+61673" - for range b.N { + for b.Loop() { jsonNumberRegexp.MatchString(s) } } diff --git a/scanner_test.go b/scanner_test.go index b540318..e7eb0ce 100644 --- a/scanner_test.go +++ b/scanner_test.go @@ -230,8 +230,7 @@ var benchScan scanner func BenchmarkSkipValue(b *testing.B) { initBig() - b.ResetTimer() - for range b.N { + for b.Loop() { _, _, _ = nextValue(jsonBig, &benchScan) } b.SetBytes(int64(len(jsonBig))) From 9e1cb0bc647ad800e4b6f70edc6a44b69dcae3de Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 9 Sep 2025 22:45:08 +0300 Subject: [PATCH 59/67] tags: use strings.FieldsFuncSeq to simplify code Signed-off-by: Roman Khimov --- tags.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/tags.go b/tags.go index c38fd51..3109208 100644 --- a/tags.go +++ b/tags.go @@ -28,17 +28,10 @@ func (o tagOptions) Contains(optionName string) bool { if len(o) == 0 { return false } - s := string(o) - for s != "" { - var next string - i := strings.Index(s, ",") - if i >= 0 { - s, next = s[:i], s[i+1:] - } + for s := range strings.FieldsFuncSeq(string(o), func(c rune) bool { return c == ',' }) { if s == optionName { return true } - s = next } return false } From d3692c847eecd77b1b092d170df20b0cec715f02 Mon Sep 17 00:00:00 2001 From: Andrey Butusov Date: Wed, 24 Sep 2025 14:47:51 +0300 Subject: [PATCH 60/67] *: fix QF1012 lint issue Use fmt.Fprintf(...) instead of WriteString(fmt.Sprintf(...)). Signed-off-by: Andrey Butusov --- encode.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/encode.go b/encode.go index c921835..a45ffd9 100644 --- a/encode.go +++ b/encode.go @@ -982,10 +982,10 @@ func (e *encodeState) string(s string, escapeHTML bool) int { e.WriteString(s[start:i]) } if c < 0x10000 { - e.WriteString(fmt.Sprintf(`\u%04X`, c)) + fmt.Fprintf(e, `\u%04X`, c) } else { r1, r2 := utf16.EncodeRune(c) - e.WriteString(fmt.Sprintf(`\u%04X\u%04X`, r1, r2)) + fmt.Fprintf(e, `\u%04X\u%04X`, r1, r2) } i += size start = i @@ -1077,10 +1077,10 @@ func (e *encodeState) stringBytes(s []byte, escapeHTML bool) int { e.Write(s[start:i]) } if c < 0x10000 { - e.WriteString(fmt.Sprintf(`\u%04X`, c)) + fmt.Fprintf(e, `\u%04X`, c) } else { r1, r2 := utf16.EncodeRune(c) - e.WriteString(fmt.Sprintf(`\u%04X\u%04X`, r1, r2)) + fmt.Fprintf(e, `\u%04X\u%04X`, r1, r2) } i += size start = i From 6424ffaeb77fcc6f3c8f9d5d29c91755b470a325 Mon Sep 17 00:00:00 2001 From: Andrey Butusov Date: Wed, 24 Sep 2025 14:48:43 +0300 Subject: [PATCH 61/67] *: fix QF1008 lint issue Could remove embedded field from selector. Signed-off-by: Andrey Butusov --- encode_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/encode_test.go b/encode_test.go index fb90013..8387eb0 100644 --- a/encode_test.go +++ b/encode_test.go @@ -496,8 +496,8 @@ func TestStringBytes(t *testing.T) { esBytes := &encodeState{} esBytes.stringBytes([]byte(s), escapeHTML) - enc := es.Buffer.String() - encBytes := esBytes.Buffer.String() + enc := es.String() + encBytes := esBytes.String() if enc != encBytes { i := 0 for i < len(enc) && i < len(encBytes) && enc[i] == encBytes[i] { From 7dbfaaa876f58b3462ba9a83bcaadc5e146fe5b8 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 8 Dec 2025 18:42:29 +0300 Subject: [PATCH 62/67] workflows: use the latest Windows version for tests Follow https://github.com/nspcc-dev/.github/blob/6c4dff04862ad947805e82fb9b664899906c6305/gh.md. Signed-off-by: Roman Khimov --- .github/workflows/run_tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index f9be4fc..e2f9b43 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -45,10 +45,10 @@ jobs: strategy: matrix: go_versions: [ '1.24', '1.25' ] - os: [ubuntu-latest, windows-2022, macos-14] + os: [ubuntu-latest, windows-latest, macos-14] exclude: # Only latest Go version for Windows and MacOS. - - os: windows-2022 + - os: windows-latest go_versions: '1.24' - os: macos-14 go_versions: '1.24' From 81cfd6f85bf868657671a9f4c2cdacba04187287 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 8 Dec 2025 18:43:00 +0300 Subject: [PATCH 63/67] workflows: use the latest MacOS runner for tests Follow https://github.com/nspcc-dev/.github/blob/6c4dff04862ad947805e82fb9b664899906c6305/gh.md. Signed-off-by: Roman Khimov --- .github/workflows/run_tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index e2f9b43..c554f77 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -45,12 +45,12 @@ jobs: strategy: matrix: go_versions: [ '1.24', '1.25' ] - os: [ubuntu-latest, windows-latest, macos-14] + os: [ubuntu-latest, windows-latest, macos-latest] exclude: # Only latest Go version for Windows and MacOS. - os: windows-latest go_versions: '1.24' - - os: macos-14 + - os: macos-latest go_versions: '1.24' # Exclude latest Go version for Ubuntu as Coverage uses it. - os: ubuntu-latest From 1d4423f56b83f7b02588762e34adb606ac3f7fd9 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 20 Feb 2026 11:17:00 +0300 Subject: [PATCH 64/67] go.mod: upgrade to Go 1.25+, fix #20 Signed-off-by: Roman Khimov --- .github/workflows/run_tests.yml | 10 +++++----- go.mod | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index c554f77..85f4841 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -27,7 +27,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: 1.25 + go-version: 1.26 - name: Write coverage profile run: go test -v ./... -coverprofile=./coverage.txt -covermode=atomic @@ -44,17 +44,17 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - go_versions: [ '1.24', '1.25' ] + go_versions: [ '1.25', '1.26' ] os: [ubuntu-latest, windows-latest, macos-latest] exclude: # Only latest Go version for Windows and MacOS. - os: windows-latest - go_versions: '1.24' + go_versions: '1.25' - os: macos-latest - go_versions: '1.24' + go_versions: '1.25' # Exclude latest Go version for Ubuntu as Coverage uses it. - os: ubuntu-latest - go_versions: '1.25' + go_versions: '1.26' fail-fast: false steps: - uses: actions/checkout@v4 diff --git a/go.mod b/go.mod index 627d352..4723659 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/nspcc-dev/go-ordered-json -go 1.24 +go 1.25 From d824a6e27ee71aa35786d3afef1d4c61a85d92b5 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 20 Feb 2026 11:20:33 +0300 Subject: [PATCH 65/67] *: use reflect.TypeAssert for more efficient casts Signed-off-by: Roman Khimov --- decode.go | 4 ++-- encode.go | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/decode.go b/decode.go index 3b4b375..e88ad87 100644 --- a/decode.go +++ b/decode.go @@ -461,11 +461,11 @@ func (d *decodeState) indirect(v reflect.Value, decodingNull bool) (Unmarshaler, v.Set(reflect.New(v.Type().Elem())) } if v.Type().NumMethod() > 0 { - if u, ok := v.Interface().(Unmarshaler); ok { + if u, ok := reflect.TypeAssert[Unmarshaler](v); ok { return u, nil, reflect.Value{} } if !decodingNull { - if u, ok := v.Interface().(encoding.TextUnmarshaler); ok { + if u, ok := reflect.TypeAssert[encoding.TextUnmarshaler](v); ok { return nil, u, reflect.Value{} } } diff --git a/encode.go b/encode.go index a45ffd9..5d44066 100644 --- a/encode.go +++ b/encode.go @@ -445,7 +445,7 @@ func marshalerEncoder(e *encodeState, v reflect.Value, opts encOpts) { e.WriteString("null") return } - m, ok := v.Interface().(Marshaler) + m, ok := reflect.TypeAssert[Marshaler](v) if !ok { e.WriteString("null") return @@ -466,7 +466,7 @@ func addrMarshalerEncoder(e *encodeState, v reflect.Value, _ encOpts) { e.WriteString("null") return } - m := va.Interface().(Marshaler) + m, _ := reflect.TypeAssert[Marshaler](va) b, err := m.MarshalJSON() if err == nil { // copy JSON into buffer, checking validity. @@ -482,7 +482,7 @@ func textMarshalerEncoder(e *encodeState, v reflect.Value, opts encOpts) { e.WriteString("null") return } - m := v.Interface().(encoding.TextMarshaler) + m, _ := reflect.TypeAssert[encoding.TextMarshaler](v) b, err := m.MarshalText() if err != nil { e.error(&MarshalerError{v.Type(), err}) @@ -496,7 +496,7 @@ func addrTextMarshalerEncoder(e *encodeState, v reflect.Value, opts encOpts) { e.WriteString("null") return } - m := va.Interface().(encoding.TextMarshaler) + m, _ := reflect.TypeAssert[encoding.TextMarshaler](va) b, err := m.MarshalText() if err != nil { e.error(&MarshalerError{v.Type(), err}) @@ -714,7 +714,7 @@ func orderedObjectEncoder(e *encodeState, v reflect.Value, opts encOpts) { return } e.WriteByte('{') - var ov = v.Interface().(OrderedObject) + var ov, _ = reflect.TypeAssert[OrderedObject](v) for i, o := range ov { if i > 0 { e.WriteByte(',') @@ -885,7 +885,7 @@ func (w *reflectWithString) resolve() error { w.s = w.v.String() return nil } - if tm, ok := w.v.Interface().(encoding.TextMarshaler); ok { + if tm, ok := reflect.TypeAssert[encoding.TextMarshaler](w.v); ok { buf, err := tm.MarshalText() w.s = string(buf) return err From 491b46ec6855dc35151f263d0124d561dffa7fb6 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 20 Feb 2026 11:33:24 +0300 Subject: [PATCH 66/67] *: ignore new staticcheck warnings for tests These things are done on purpose. Signed-off-by: Roman Khimov --- decode_test.go | 2 +- tagkey_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/decode_test.go b/decode_test.go index 2e5ef37..f569225 100644 --- a/decode_test.go +++ b/decode_test.go @@ -1890,7 +1890,7 @@ func TestUnmarshalSyntax(t *testing.T) { type unexportedFields struct { Name string m map[string]any `json:"-"` //nolint:unused // Not really used, but important for test. - m2 map[string]any `json:"abcd"` //nolint:unused,govet // Not really used and wrong, but important for test. + m2 map[string]any `json:"abcd"` //nolint:unused,govet,staticcheck // Not really used and wrong, but important for test. } func TestUnmarshalUnexported(t *testing.T) { diff --git a/tagkey_test.go b/tagkey_test.go index cfd7dc2..b388844 100644 --- a/tagkey_test.go +++ b/tagkey_test.go @@ -45,7 +45,7 @@ type punctuationTag struct { } type dashTag struct { - V string `json:"-,"` + V string `json:"-,"` // nolint:staticcheck // The test is written this way } type emptyTag struct { From afae2a6ff2bd0305b963fcfa1dbb6f4baf502991 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 27 Feb 2026 18:26:27 +0300 Subject: [PATCH 67/67] *: apply `modernize` suggestions Use https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize to improve our code. Signed-off-by: Anna Shaleva --- decode.go | 11 ++++------- decode_test.go | 34 +++++++++++++++++----------------- encode.go | 11 +++++------ encode_test.go | 2 +- scanner_test.go | 15 +++------------ tags.go | 4 ++-- 6 files changed, 32 insertions(+), 45 deletions(-) diff --git a/decode.go b/decode.go index e88ad87..ac9bd4d 100644 --- a/decode.go +++ b/decode.go @@ -532,10 +532,7 @@ func (d *decodeState) array(v reflect.Value) { if v.Kind() == reflect.Slice { // Grow slice if necessary if i >= v.Cap() { - newcap := v.Cap() + v.Cap()/2 - if newcap < 4 { - newcap = 4 - } + newcap := max(v.Cap()+v.Cap()/2, 4) newv := reflect.MakeSlice(v.Type(), v.Len(), newcap) reflect.Copy(newv, v) v.Set(newv) @@ -581,7 +578,7 @@ func (d *decodeState) array(v reflect.Value) { } var nullLiteral = []byte("null") -var textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem() +var textUnmarshalerType = reflect.TypeFor[encoding.TextUnmarshaler]() // object consumes an object from d.data[d.off-1:], decoding into the value v. // the first byte ('{') of the object has been read already. @@ -808,12 +805,12 @@ func (d *decodeState) convertNumber(s string) (any, error) { } f, err := strconv.ParseFloat(s, 64) if err != nil { - return nil, &UnmarshalTypeError{Value: "number " + s, Type: reflect.TypeOf(0.0), Offset: int64(d.off)} + return nil, &UnmarshalTypeError{Value: "number " + s, Type: reflect.TypeFor[float64](), Offset: int64(d.off)} } return f, nil } -var numberType = reflect.TypeOf(Number("")) +var numberType = reflect.TypeFor[Number]() // literalStore decodes a literal stored in item into v. // diff --git a/decode_test.go b/decode_test.go index f569225..f18eb55 100644 --- a/decode_test.go +++ b/decode_test.go @@ -88,11 +88,11 @@ func (u unmarshalerText) MarshalText() ([]byte, error) { } func (u *unmarshalerText) UnmarshalText(b []byte) error { - pos := bytes.Index(b, []byte(":")) - if pos == -1 { + before, after, ok := bytes.Cut(b, []byte(":")) + if !ok { return errors.New("missing separator") } - u.A, u.B = string(b[:pos]), string(b[pos+1:]) + u.A, u.B = string(before), string(after) return nil } @@ -106,7 +106,7 @@ type ustructText struct { type u8marshal uint8 func (u8 u8marshal) MarshalText() ([]byte, error) { - return []byte(fmt.Sprintf("u%d", u8)), nil + return fmt.Appendf(nil, "u%d", u8), nil } var errMissingU8Prefix = errors.New("missing 'u' prefix") @@ -262,7 +262,7 @@ func mapAddr(x map[string]int) *map[string]int { return &x } type byteWithMarshalJSON byte func (b byteWithMarshalJSON) MarshalJSON() ([]byte, error) { - return []byte(fmt.Sprintf(`"Z%.2x"`, byte(b))), nil + return fmt.Appendf(nil, `"Z%.2x"`, byte(b)), nil } func (b *byteWithMarshalJSON) UnmarshalJSON(data []byte) error { @@ -290,7 +290,7 @@ func (b *byteWithPtrMarshalJSON) UnmarshalJSON(data []byte) error { type byteWithMarshalText byte func (b byteWithMarshalText) MarshalText() ([]byte, error) { - return []byte(fmt.Sprintf(`Z%.2x`, byte(b))), nil + return fmt.Appendf(nil, `Z%.2x`, byte(b)), nil } func (b *byteWithMarshalText) UnmarshalText(data []byte) error { @@ -318,7 +318,7 @@ func (b *byteWithPtrMarshalText) UnmarshalText(data []byte) error { type intWithMarshalJSON int func (b intWithMarshalJSON) MarshalJSON() ([]byte, error) { - return []byte(fmt.Sprintf(`"Z%.2x"`, int(b))), nil + return fmt.Appendf(nil, `"Z%.2x"`, int(b)), nil } func (b *intWithMarshalJSON) UnmarshalJSON(data []byte) error { @@ -346,7 +346,7 @@ func (b *intWithPtrMarshalJSON) UnmarshalJSON(data []byte) error { type intWithMarshalText int func (b intWithMarshalText) MarshalText() ([]byte, error) { - return []byte(fmt.Sprintf(`Z%.2x`, int(b))), nil + return fmt.Appendf(nil, `Z%.2x`, int(b)), nil } func (b *intWithMarshalText) UnmarshalText(data []byte) error { @@ -400,7 +400,7 @@ var unmarshalTests = []unmarshalTest{ {in: `"g-clef: \uD834\uDD1E"`, ptr: new(string), out: "g-clef: \U0001D11E"}, {in: `"invalid: \uD834x\uDD1E"`, ptr: new(string), out: "invalid: \uFFFDx\uFFFD"}, {in: "null", ptr: new(any), out: nil}, - {in: `{"X": [1,2,3], "Y": 4}`, ptr: new(T), out: T{Y: 4}, err: &UnmarshalTypeError{"array", reflect.TypeOf(""), 7, "T", "X"}}, + {in: `{"X": [1,2,3], "Y": 4}`, ptr: new(T), out: T{Y: 4}, err: &UnmarshalTypeError{"array", reflect.TypeFor[string](), 7, "T", "X"}}, {in: `{"x": 1}`, ptr: new(tx), out: tx{}}, {in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: float64(1), F2: int32(2), F3: Number("3")}}, {in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: Number("1"), F2: int32(2), F3: Number("3")}, useNumber: true}, @@ -515,22 +515,22 @@ var unmarshalTests = []unmarshalTest{ { in: `{"abc":"abc"}`, ptr: new(map[int]string), - err: &UnmarshalTypeError{Value: "number abc", Type: reflect.TypeOf(0), Offset: 2}, + err: &UnmarshalTypeError{Value: "number abc", Type: reflect.TypeFor[int](), Offset: 2}, }, { in: `{"256":"abc"}`, ptr: new(map[uint8]string), - err: &UnmarshalTypeError{Value: "number 256", Type: reflect.TypeOf(uint8(0)), Offset: 2}, + err: &UnmarshalTypeError{Value: "number 256", Type: reflect.TypeFor[uint8](), Offset: 2}, }, { in: `{"128":"abc"}`, ptr: new(map[int8]string), - err: &UnmarshalTypeError{Value: "number 128", Type: reflect.TypeOf(int8(0)), Offset: 2}, + err: &UnmarshalTypeError{Value: "number 128", Type: reflect.TypeFor[int8](), Offset: 2}, }, { in: `{"-1":"abc"}`, ptr: new(map[uint8]string), - err: &UnmarshalTypeError{Value: "number -1", Type: reflect.TypeOf(uint8(0)), Offset: 2}, + err: &UnmarshalTypeError{Value: "number -1", Type: reflect.TypeFor[uint8](), Offset: 2}, }, // Map keys can be encoding.TextUnmarshalers. @@ -664,12 +664,12 @@ var unmarshalTests = []unmarshalTest{ { in: `{"2009-11-10T23:00:00Z": "hello world"}`, ptr: &map[Point]string{}, - err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeOf(map[Point]string{}), Offset: 1}, + err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeFor[map[Point]string](), Offset: 1}, }, { in: `{"asdf": "hello world"}`, ptr: &map[unmarshaler]string{}, - err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeOf(map[unmarshaler]string{}), Offset: 1}, + err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeFor[map[unmarshaler]string](), Offset: 1}, }, // related to issue 13783. @@ -769,7 +769,7 @@ var unmarshalTests = []unmarshalTest{ Value: "string", Struct: "V", Field: "F2", - Type: reflect.TypeOf(int32(0)), + Type: reflect.TypeFor[int32](), Offset: 20, }, }, @@ -780,7 +780,7 @@ var unmarshalTests = []unmarshalTest{ Value: "string", Struct: "V", Field: "F2", - Type: reflect.TypeOf(int32(0)), + Type: reflect.TypeFor[int32](), Offset: 30, }, }, diff --git a/encode.go b/encode.go index 5d44066..c2f4c28 100644 --- a/encode.go +++ b/encode.go @@ -15,6 +15,7 @@ import ( "encoding" "encoding/base64" "fmt" + "maps" "math" "reflect" "runtime" @@ -376,9 +377,9 @@ func typeEncoder(t reflect.Type) encoderFunc { } var ( - marshalerType = reflect.TypeOf(new(Marshaler)).Elem() - textMarshalerType = reflect.TypeOf(new(encoding.TextMarshaler)).Elem() - orderedObjectType = reflect.TypeOf(new(OrderedObject)).Elem() + marshalerType = reflect.TypeFor[Marshaler]() + textMarshalerType = reflect.TypeFor[encoding.TextMarshaler]() + orderedObjectType = reflect.TypeFor[OrderedObject]() ) // newTypeEncoder constructs an encoderFunc for a type. @@ -1334,9 +1335,7 @@ func cachedTypeFields(t reflect.Type) []field { fieldCache.mu.Lock() m, _ = fieldCache.value.Load().(map[reflect.Type][]field) newM := make(map[reflect.Type][]field, len(m)+1) - for k, v := range m { - newM[k] = v - } + maps.Copy(newM, m) newM[t] = f fieldCache.value.Store(newM) fieldCache.mu.Unlock() diff --git a/encode_test.go b/encode_test.go index 8387eb0..6e2aead 100644 --- a/encode_test.go +++ b/encode_test.go @@ -40,7 +40,7 @@ type Optionals struct { Uo uint `json:"uo,omitempty"` Str struct{} `json:"str"` - Sto struct{} `json:"sto,omitempty"` + Sto struct{} `json:"sto"` } var optionalsExpected = `{ diff --git a/scanner_test.go b/scanner_test.go index e7eb0ce..5ae9913 100644 --- a/scanner_test.go +++ b/scanner_test.go @@ -239,10 +239,7 @@ func BenchmarkSkipValue(b *testing.B) { func diff(t *testing.T, a, b []byte) { for i := 0; ; i++ { if i >= len(a) || i >= len(b) || a[i] != b[i] { - j := i - 10 - if j < 0 { - j = 0 - } + j := max(i-10, 0) t.Errorf("diverge at %d: «%s» vs «%s»", i, trim(a[j:]), trim(b[j:])) return } @@ -306,10 +303,7 @@ func genString(stddev float64) string { } func genArray(n int) []any { - f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2))) - if f > n { - f = n - } + f := min(int(math.Abs(rand.NormFloat64())*math.Min(10, float64(n/2))), n) if f < 1 { f = 1 } @@ -321,10 +315,7 @@ func genArray(n int) []any { } func genMap(n int) map[string]any { - f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2))) - if f > n { - f = n - } + f := min(int(math.Abs(rand.NormFloat64())*math.Min(10, float64(n/2))), n) if n > 0 && f == 0 { f = 1 } diff --git a/tags.go b/tags.go index 3109208..1fc0817 100644 --- a/tags.go +++ b/tags.go @@ -15,8 +15,8 @@ type tagOptions string // parseTag splits a struct field's json tag into its name and // comma-separated options. func parseTag(tag string) (string, tagOptions) { - if idx := strings.Index(tag, ","); idx != -1 { - return tag[:idx], tagOptions(tag[idx+1:]) + if before, after, ok := strings.Cut(tag, ","); ok { + return before, tagOptions(after) } return tag, tagOptions("") }