From ced28302a987f38e47a256a1c097e49df320c55b Mon Sep 17 00:00:00 2001 From: Yi Duan Date: Wed, 25 May 2022 14:16:56 +0800 Subject: [PATCH] feat: add compatible API (#233) * feat: add compatible API * test: add api compatibility test * fmt * test: adjust CI * fix: no trailing '\n' for std `Marshal()` * doc: update README.md * test: add windows CI * doc: update README.md * doc: update README.md * fmt: add comments and refator --- ...enchmark.yml => benchmark-linux-amd64.yml} | 0 .github/workflows/codeql-analysis.yml | 2 +- ...ush-check-linux.yml => push-check-all.yml} | 7 +- .github/workflows/push-check-arm.yml | 27 + ...k-linux-go118.yml => push-check-go118.yml} | 2 +- .github/workflows/push-check-windows.yml | 43 +- README.md | 13 +- api.go | 127 + api_test.go | 262 ++ compat.go | 117 + decode_float_test.go | 234 +- decode_test.go | 3768 +++++++++-------- decoder/decoder.go | 18 + encode_test.go | 1682 ++++---- search_test.go | 2 + sonic.go | 128 +- use_number_test.go | 146 +- 17 files changed, 3608 insertions(+), 2970 deletions(-) rename .github/workflows/{benchmark.yml => benchmark-linux-amd64.yml} (100%) rename .github/workflows/{push-check-linux.yml => push-check-all.yml} (78%) create mode 100644 .github/workflows/push-check-arm.yml rename .github/workflows/{push-check-linux-go118.yml => push-check-go118.yml} (87%) create mode 100644 api.go create mode 100644 api_test.go create mode 100644 compat.go diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark-linux-amd64.yml similarity index 100% rename from .github/workflows/benchmark.yml rename to .github/workflows/benchmark-linux-amd64.yml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 4cf7318..82c6181 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -55,7 +55,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + 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. diff --git a/.github/workflows/push-check-linux.yml b/.github/workflows/push-check-all.yml similarity index 78% rename from .github/workflows/push-check-linux.yml rename to .github/workflows/push-check-all.yml index de8ac97..fcb9f93 100644 --- a/.github/workflows/push-check-linux.yml +++ b/.github/workflows/push-check-all.yml @@ -1,4 +1,4 @@ -name: Push Check Linux +name: Push Check All on: push @@ -7,7 +7,8 @@ jobs: strategy: matrix: go-version: [1.15.x, 1.16.x, 1.17.x] - runs-on: self-hosted + os: [self-hosted] + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 @@ -24,4 +25,4 @@ jobs: ${{ runner.os }}-go- - name: Unit Test - run: GOMAXPROCS=4 go test -v -gcflags=-d=checkptr=0 ./... + run: go test -v -gcflags=-d=checkptr=0 ./... diff --git a/.github/workflows/push-check-arm.yml b/.github/workflows/push-check-arm.yml new file mode 100644 index 0000000..544953d --- /dev/null +++ b/.github/workflows/push-check-arm.yml @@ -0,0 +1,27 @@ +name: Push Check ARM + +on: push + +jobs: + build: + strategy: + matrix: + go-version: [1.15.x, 1.18.x] + runs-on: [self-hosted, arm] + steps: + - uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go-version }} + + - uses: actions/cache@v2 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Compatibility Test + run: go test -v -gcflags=-d=checkptr=0 . diff --git a/.github/workflows/push-check-linux-go118.yml b/.github/workflows/push-check-go118.yml similarity index 87% rename from .github/workflows/push-check-linux-go118.yml rename to .github/workflows/push-check-go118.yml index 2d4a5dd..3a4a7dc 100644 --- a/.github/workflows/push-check-linux-go118.yml +++ b/.github/workflows/push-check-go118.yml @@ -24,4 +24,4 @@ jobs: run: GOMAXPROCS=4 go test -v -gcflags=-d=checkptr=0 -race ./... - name: Generic Test - run: go test -v -gcflags=-d=checkptr=0 -race ./generic_test + run: GOMAXPROCS=4 go test -v -gcflags=-d=checkptr=0 -race ./generic_test diff --git a/.github/workflows/push-check-windows.yml b/.github/workflows/push-check-windows.yml index 85b4efb..8499651 100644 --- a/.github/workflows/push-check-windows.yml +++ b/.github/workflows/push-check-windows.yml @@ -1,26 +1,27 @@ -# name: Push Check Go Windows +name: Push Check Windows -# on: push +on: push -# jobs: -# build: -# strategy: -# matrix: -# go-version: [1.15.x, 1.16.x, 1.17.x, 1.18.x] -# os: [windows-latest] -# runs-on: ${{ matrix.os }} -# steps: -# - uses: actions/checkout@v2 +jobs: + build: + strategy: + matrix: + go-version: [1.15.x, 1.18.x] + runs-on: windows-latest + steps: + - uses: actions/checkout@v2 -# - name: Set up Go -# uses: actions/setup-go@v3 -# with: -# go-version: ${{ matrix.go-version }} + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go-version }} -# - name: Unit Test -# run: go test -v -gcflags -d=checkptr=0 -covermode atomic -coverprofile coverage.out ./... -# env: -# GOMAXPROCS: 4 + - uses: actions/cache@v2 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- -# - name: Generic Test -# run: go test -v -gcflags -d=checkptr=0 -covermode atomic ./generic_test \ No newline at end of file + - name: Compatibility Test + run: go test -v -gcflags=-d=checkptr=0 . diff --git a/README.md b/README.md index 07a5a0c..813213c 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A blazingly fast JSON serializing & deserializing library, accelerated by JI ## Requirement - Go 1.15/1.16/1.17/1.18 -- Linux/darwin OS +- Linux/MacOS/Windows - Amd64 CPU with AVX instruction set ## Features @@ -260,6 +260,12 @@ println(string(buf) == string(exp)) // true - iteration: `Values()`, `Properties()`, `ForEach()`, `SortKeys()` - modification: `Set()`, `SetByIndex()`, `Add()` +## Compatibility +Sonic **DOSE NOT** ensure to support all environments, due to the difficulty of developing high-performance codes. For developers who use sonic to build their applications in different environments (ex: developing on M1 Mac but running on linux server), or those who want to handle JSON strictly consistent with `encoding/json`, we provide some compatible APIs as `sonic.API` +- `ConfigDefault`: the sonic's default config (`EscapeHTML=false`,`SortKeys=false`...) to run on sonic-supporting environment. It will fall back to `encoding/json` with corresponding config , and some options like `SortKeys=false` will be invalid. +- `ConfigStd`: the std-compatible config (`EscapeHTML=true`,`SortKeys=true`...) to run on sonic-supporting environment. It whill fall back to `encoding/json`. +- `ConfigFastest`: the fastest config (`NoQuoteTextMarshaler=true`) to run on sonic-supporting environment. It will fall back to `encoding/json` with corresponding config , and some options will be invalid. + ## Tips ### Pretouch @@ -316,4 +322,7 @@ Why? Because `ast.Node` stores its children using `array`: - **Hashing** (`map[x]`) is not as efficient as **Indexing** (`array[x]`), which `ast.Node` can conduct on **both array and object**; - Using `Interface()`/`Map()` means Sonic must parse all the underlying values, while `ast.Node` can parse them **on demand**. -**CAUTION:** `ast.Node` **DOESN'T** ensure concurrent security directly, due to its **lazy-load** design. However, your can call `Node.Load()`/`Node.LoadAll()` to achieve that, which may bring performance reduction while it still works faster than converting to `map` or `interface{}` \ No newline at end of file +**CAUTION:** `ast.Node` **DOESN'T** ensure concurrent security directly, due to its **lazy-load** design. However, your can call `Node.Load()`/`Node.LoadAll()` to achieve that, which may bring performance reduction while it still works faster than converting to `map` or `interface{}` + +## Community +Sonic is a subproject of [CloudWeGo](https://www.cloudwego.io/). We are committed to building a cloud native ecosystem. \ No newline at end of file diff --git a/api.go b/api.go new file mode 100644 index 0000000..8b119cc --- /dev/null +++ b/api.go @@ -0,0 +1,127 @@ +/* + * Copyright 2021 ByteDance Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sonic + +import ( + `io` +) + + // Config is a combination of sonic/encoder.Options and sonic/decoder.Options + type Config struct { + EscapeHTML bool + SortMapKeys bool + CompactMarshaler bool + NoQuoteTextMarshaler bool + UseInt64 bool + UseNumber bool + UseUnicodeErrors bool + DisallowUnknownFields bool + CopyString bool + } + + var ( + // ConfigDefault is the default config of APIs, aiming at efficiency and safty. + ConfigDefault = Config{}.Froze() + + // ConfigStd is the standard config of APIs, aiming at being compatible with encoding/json. + ConfigStd = Config{ + EscapeHTML : true, + SortMapKeys: true, + CompactMarshaler: true, + CopyString : true, + }.Froze() + + // ConfigFastest is the fastest config of APIs, aiming at speed. + ConfigFastest = Config{ + NoQuoteTextMarshaler: true, + }.Froze() + ) + + + // API a binding of specific config. + // This interface is inspired by github.com/json-iterator/go, + // and has same behaviors under equavilent config. + type API interface { + // MarshalToString returns the JSON encoding string of v + MarshalToString(v interface{}) (string, error) + // Marshal returns the JSON encoding bytes of v. + Marshal(v interface{}) ([]byte, error) + // MarshalIndent returns the JSON encoding bytes with indent and prefix. + MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) + // UnmarshalFromString parses the JSON-encoded bytes and stores the result in the value pointed to by v. + UnmarshalFromString(str string, v interface{}) error + // Unmarshal parses the JSON-encoded string and stores the result in the value pointed to by v. + Unmarshal(data []byte, v interface{}) error + // NewEncoder create a Encoder holding writer + NewEncoder(writer io.Writer) Encoder + // NewDecoder create a Decoder holding reader + NewDecoder(reader io.Reader) Decoder + // Valid validates the JSON-encoded bytes and reportes if it is valid + Valid(data []byte) bool + } + + // Encoder encodes JSON into io.Writer + type Encoder interface { + // Encode writes the JSON encoding of v to the stream, followed by a newline character. + Encode(val interface{}) error + // SetEscapeHTML specifies whether problematic HTML characters + // should be escaped inside JSON quoted strings. + // The default behavior NOT ESCAPE + SetEscapeHTML(on bool) + // SetIndent instructs the encoder to format each subsequent encoded value + // as if indented by the package-level function Indent(dst, src, prefix, indent). + // Calling SetIndent("", "") disables indentation + SetIndent(prefix, indent string) + } + + // Decoder decodes JSON from io.Read + type Decoder interface { + // Decode reads the next JSON-encoded value from its input and stores it in the value pointed to by v. + Decode(val interface{}) error + // Buffered returns a reader of the data remaining in the Decoder's buffer. + // The reader is valid until the next call to Decode. + Buffered() io.Reader + // DisallowUnknownFields causes the Decoder to return an error when the destination is a struct + // and the input contains object keys which do not match any non-ignored, exported fields in the destination. + DisallowUnknownFields() + // More reports whether there is another element in the current array or object being parsed. + More() bool + // UseNumber causes the Decoder to unmarshal a number into an interface{} as a Number instead of as a float64. + UseNumber() + } + + // Marshal returns the JSON encoding bytes of v. +func Marshal(val interface{}) ([]byte, error) { + return ConfigDefault.Marshal(val) +} + +// MarshalString returns the JSON encoding string of v. +func MarshalString(val interface{}) (string, error) { + return ConfigDefault.MarshalToString(val) +} + +// Unmarshal parses the JSON-encoded data and stores the result in the value pointed to by v. +// NOTICE: This API copies given buffer by default, +// if you want to pass JSON more efficiently, use UnmarshalString instead. +func Unmarshal(buf []byte, val interface{}) error { + return ConfigDefault.Unmarshal(buf, val) +} + +// UnmarshalString is like Unmarshal, except buf is a string. +func UnmarshalString(buf string, val interface{}) error { + return ConfigDefault.UnmarshalFromString(buf, val) +} diff --git a/api_test.go b/api_test.go new file mode 100644 index 0000000..b43295c --- /dev/null +++ b/api_test.go @@ -0,0 +1,262 @@ +/* + * Copyright 2021 ByteDance Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sonic + +import ( + `bytes` + `encoding/json` + `testing` + + jsoniter `github.com/json-iterator/go` + `github.com/stretchr/testify/require` +) + +var jt = jsoniter.Config{ + ValidateJsonRawMessage: true, +}.Froze() + +func TestCompatMarshalDefault(t *testing.T){ + var obj = map[string]interface{}{ + "c": json.RawMessage("[\"<&>\"]"), + } + sout, serr := ConfigDefault.Marshal(obj) + jout, jerr := jt.Marshal(obj) + require.Equal(t, jerr, serr) + require.Equal(t, string(jout), string(sout)) + + // obj = map[string]interface{}{ + // "a": json.RawMessage(" [} "), + // } + // sout, serr = ConfigDefault.Marshal(obj) + // jout, jerr = json.Marshal(obj) + // require.NotNil(t, jerr) + // require.NotNil(t, serr) + // require.Equal(t, string(jout), string(sout)) + + obj = map[string]interface{}{ + "a": json.RawMessage("1"), + } + sout, serr = ConfigDefault.MarshalIndent(obj, "", " ") + jout, jerr = jt.MarshalIndent(obj, "", " ") + require.Equal(t, jerr, serr) + require.Equal(t, string(jout), string(sout)) +} + +func TestCompatMarshalStd(t *testing.T) { + t.Parallel() + var obj = map[string]interface{}{ + "c": json.RawMessage(" [ \"<&>\" ] "), + "b": json.RawMessage(" [ ] "), + } + sout, serr := ConfigStd.Marshal(obj) + jout, jerr := json.Marshal(obj) + require.Equal(t, jerr, serr) + require.Equal(t, string(jout), string(sout)) + + obj = map[string]interface{}{ + "a": json.RawMessage(" [} "), + } + sout, serr = ConfigStd.Marshal(obj) + jout, jerr = json.Marshal(obj) + require.NotNil(t, jerr) + require.NotNil(t, serr) + require.Equal(t, string(jout), string(sout)) + + obj = map[string]interface{}{ + "a": json.RawMessage("1"), + } + sout, serr = ConfigStd.MarshalIndent(obj, "xxxx", " ") + jout, jerr = json.MarshalIndent(obj, "xxxx", " ") + require.Equal(t, jerr, serr) + require.Equal(t, string(jout), string(sout)) +} + +func TestCompatUnmarshalDefault(t *testing.T) { + var sobj = map[string]interface{}{} + var jobj = map[string]interface{}{} + var data = []byte(`{"a":-0}`) + var str = string(data) + serr := ConfigDefault.UnmarshalFromString(str, &sobj) + jerr := jt.UnmarshalFromString(str, &jobj) + require.Equal(t, jerr, serr) + require.Equal(t, jobj, sobj) + + x := struct{A json.Number}{} + y := struct{A json.Number}{} + data = []byte(`{"A":"1", "B":-1}`) + serr = ConfigDefault.Unmarshal(data, &x) + jerr = jt.Unmarshal(data, &y) + require.Equal(t, jerr, serr) + require.Equal(t, y, x) +} + +func TestCompatUnmarshalStd(t *testing.T) { + var sobj = map[string]interface{}{} + var jobj = map[string]interface{}{} + var data = []byte(`{"a":1.00000001E-10}`) + var str = string(data) + serr := ConfigStd.UnmarshalFromString(str, &sobj) + jerr := json.Unmarshal(data, &jobj) + require.Equal(t, jerr, serr) + require.Equal(t, jobj, sobj) + data[2] = '0' + require.Equal(t, jobj, sobj) + + sobj = map[string]interface{}{} + jobj = map[string]interface{}{} + data = []byte(`{"a":1}`) + cfg := Config{ + UseNumber: true, + }.Froze() + serr = cfg.Unmarshal(data, &sobj) + dec := json.NewDecoder(bytes.NewBuffer(data)) + dec.UseNumber() + jerr = dec.Decode(&jobj) + require.Equal(t, jerr, serr) + require.Equal(t, jobj, sobj) + + x := struct{A json.Number}{} + y := struct{A json.Number}{} + data = []byte(`{"A":"1", "B":-1}`) + cfg = Config{ + DisallowUnknownFields: true, + }.Froze() + serr = cfg.Unmarshal(data, &x) + dec = json.NewDecoder(bytes.NewBuffer(data)) + dec.UseNumber() + dec.DisallowUnknownFields() + jerr = dec.Decode(&y) + require.Equal(t, jerr, serr) + require.Equal(t, y, x) +} + +func TestCompatEncoderDefault(t *testing.T) { + var o = map[string]interface{}{ + "a": "<>", + // "b": json.RawMessage(" [ ] "), + } + var w1 = bytes.NewBuffer(nil) + var w2 = bytes.NewBuffer(nil) + var enc1 = jt.NewEncoder(w1) + var enc2 = ConfigDefault.NewEncoder(w2) + + require.Nil(t, enc1.Encode(o)) + require.Nil(t, enc2.Encode(o)) + require.Equal(t, w1.String(), w2.String()) + + enc1.SetEscapeHTML(true) + enc2.SetEscapeHTML(true) + enc1.SetIndent("", " ") + enc2.SetIndent("", " ") + require.Nil(t, enc1.Encode(o)) + require.Nil(t, enc2.Encode(o)) + require.Equal(t, w1.String(), w2.String()) + + enc1.SetEscapeHTML(false) + enc2.SetEscapeHTML(false) + enc1.SetIndent("", "") + enc2.SetIndent("", "") + require.Nil(t, enc1.Encode(o)) + require.Nil(t, enc2.Encode(o)) + require.Equal(t, w1.String(), w2.String()) +} + +func TestCompatEncoderStd(t *testing.T) { + var o = map[string]interface{}{ + "a": "<>", + "b": json.RawMessage(" [ ] "), + } + var w1 = bytes.NewBuffer(nil) + var w2 = bytes.NewBuffer(nil) + var enc1 = json.NewEncoder(w1) + var enc2 = ConfigStd.NewEncoder(w2) + + require.Nil(t, enc1.Encode(o)) + require.Nil(t, enc2.Encode(o)) + require.Equal(t, w1.String(), w2.String()) + + enc1.SetEscapeHTML(true) + enc2.SetEscapeHTML(true) + enc1.SetIndent("\n", " ") + enc2.SetIndent("\n", " ") + require.Nil(t, enc1.Encode(o)) + require.Nil(t, enc2.Encode(o)) + require.Equal(t, w1.String(), w2.String()) + + enc1.SetEscapeHTML(false) + enc2.SetEscapeHTML(false) + enc1.SetIndent("", "") + enc2.SetIndent("", "") + require.Nil(t, enc1.Encode(o)) + require.Nil(t, enc2.Encode(o)) + require.Equal(t, w1.String(), w2.String()) +} + +func TestCompatDecoderStd(t *testing.T) { + var o1 = map[string]interface{}{} + var o2 = map[string]interface{}{} + var s = `{"a":"b"} {"1":"2"} a {}` + var w1 = bytes.NewBuffer([]byte(s)) + var w2 = bytes.NewBuffer([]byte(s)) + var enc1 = json.NewDecoder(w1) + var enc2 = ConfigStd.NewDecoder(w2) + + require.Equal(t, enc1.More(), enc2.More()) + require.Nil(t, enc1.Decode(&o1)) + require.Nil(t, enc2.Decode(&o2)) + require.Equal(t, w1.String(), w2.String()) + + require.Equal(t, enc1.More(), enc2.More()) + require.Nil(t, enc1.Decode(&o1)) + require.Nil(t, enc2.Decode(&o2)) + require.Equal(t, w1.String(), w2.String()) + + require.Equal(t, enc1.More(), enc2.More()) + require.NotNil(t, enc1.Decode(&o1)) + require.NotNil(t, enc2.Decode(&o2)) + require.Equal(t, w1.String(), w2.String()) +} + +func TestCompatDecoderDefault(t *testing.T) { + var o1 = map[string]interface{}{} + var o2 = map[string]interface{}{} + var s = `{"a":"b"} {"1":"2"} a {}` + var w1 = bytes.NewBuffer([]byte(s)) + var w2 = bytes.NewBuffer([]byte(s)) + var enc1 = jt.NewDecoder(w1) + var enc2 = ConfigDefault.NewDecoder(w2) + + require.Equal(t, enc1.More(), enc2.More()) + require.Nil(t, enc1.Decode(&o1)) + require.Nil(t, enc2.Decode(&o2)) + require.Equal(t, w1.String(), w2.String()) + + require.Equal(t, enc1.More(), enc2.More()) + require.Nil(t, enc1.Decode(&o1)) + require.Nil(t, enc2.Decode(&o2)) + require.Equal(t, w1.String(), w2.String()) + + require.Equal(t, enc1.More(), enc2.More()) + require.NotNil(t, enc1.Decode(&o1)) + require.NotNil(t, enc2.Decode(&o2)) + require.Equal(t, w1.String(), w2.String()) + + // require.Equal(t, enc1.More(), enc2.More()) + // require.NotNil(t, enc1.Decode(&o1)) + // require.NotNil(t, enc2.Decode(&o2)) + // require.Equal(t, w1.String(), w2.String()) +} \ No newline at end of file diff --git a/compat.go b/compat.go new file mode 100644 index 0000000..4e9f79c --- /dev/null +++ b/compat.go @@ -0,0 +1,117 @@ +// +build !amd64 + +/* + * Copyright 2021 ByteDance Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sonic + +import ( + `bytes` + `encoding/json` + `io` +) + +type frozenConfig struct { + Config +} + +// Froze convert the Config to API +func (cfg Config) Froze() API { + api := &frozenConfig{Config: cfg} + return api +} + +func (cfg *frozenConfig) marshalOptions(val interface{}, prefix, indent string) ([]byte, error) { + w := bytes.NewBuffer([]byte{}) + enc := json.NewEncoder(w) + enc.SetEscapeHTML(cfg.EscapeHTML) + enc.SetIndent(prefix, indent) + err := enc.Encode(val) + out := w.Bytes() + + // json.Encoder always appends '\n' after encoding, + // which is not same with json.Marshal() + if len(out) > 0 && out[len(out)-1] == '\n' { + out = out[:len(out)-1] + } + return out, err +} + +// Marshal is implemented by sonic +func (cfg *frozenConfig) Marshal(val interface{}) ([]byte, error) { + if !cfg.EscapeHTML { + return cfg.marshalOptions(val, "", "") + } + return json.Marshal(val) +} + +// MarshalToString is implemented by sonic +func (cfg *frozenConfig) MarshalToString(val interface{}) (string, error) { + out, err := cfg.Marshal(val) + return string(out), err +} + +// MarshalIndent is implemented by sonic +func (cfg *frozenConfig) MarshalIndent(val interface{}, prefix, indent string) ([]byte, error) { + if !cfg.EscapeHTML { + return cfg.marshalOptions(val, prefix, indent) + } + return json.MarshalIndent(val, prefix, indent) +} + +// UnmarshalFromString is implemented by sonic +func (cfg *frozenConfig) UnmarshalFromString(buf string, val interface{}) error { + r := bytes.NewBufferString(buf) + dec := json.NewDecoder(r) + if cfg.UseNumber { + dec.UseNumber() + } + if cfg.DisallowUnknownFields { + dec.DisallowUnknownFields() + } + return dec.Decode(val) +} + +// Unmarshal is implemented by sonic +func (cfg *frozenConfig) Unmarshal(buf []byte, val interface{}) error { + return cfg.UnmarshalFromString(string(buf), val) +} + +// NewEncoder is implemented by sonic +func (cfg *frozenConfig) NewEncoder(writer io.Writer) Encoder { + enc := json.NewEncoder(writer) + if !cfg.EscapeHTML { + enc.SetEscapeHTML(cfg.EscapeHTML) + } + return enc +} + +// NewDecoder is implemented by sonic +func (cfg *frozenConfig) NewDecoder(reader io.Reader) Decoder { + dec := json.NewDecoder(reader) + if cfg.UseNumber { + dec.UseNumber() + } + if cfg.DisallowUnknownFields { + dec.DisallowUnknownFields() + } + return dec +} + +// Valid is implemented by sonic +func (cfg *frozenConfig) Valid(data []byte) bool { + return json.Valid(data) +} \ No newline at end of file diff --git a/decode_float_test.go b/decode_float_test.go index afea21c..009495b 100644 --- a/decode_float_test.go +++ b/decode_float_test.go @@ -1,3 +1,5 @@ +// +build amd64 + /* * Copyright 2021 ByteDance Inc. * @@ -17,146 +19,146 @@ package sonic import ( - "encoding/json" - "reflect" - "strings" - "testing" + `encoding/json` + `reflect` + `strings` + `testing` - "github.com/bytedance/sonic/decoder" + `github.com/bytedance/sonic/decoder` ) type atofTest struct { - in string - out string - err error + in string + out string + err error } // Tests from Go strconv package, https://github.com/golang/go/blob/master/src/strconv/atof_test.go // All tests are passed in Go encoding/json. var atoftests = []atofTest{ - {"1.234e", "", nil}, // error - {"1i", "1", nil}, // pass - {"1", "1", nil}, - {"1e23", "1e+23", nil}, - {"1E23", "1e+23", nil}, - {"100000000000000000000000", "1e+23", nil}, - {"1e-100", "1e-100", nil}, - {"123456700", "1.234567e+08", nil}, - {"99999999999999974834176", "9.999999999999997e+22", nil}, - {"100000000000000000000001", "1.0000000000000001e+23", nil}, - {"100000000000000008388608", "1.0000000000000001e+23", nil}, - {"100000000000000016777215", "1.0000000000000001e+23", nil}, - {"100000000000000016777216", "1.0000000000000003e+23", nil}, - {"-1", "-1", nil}, - {"-0.1", "-0.1", nil}, - {"-0", "-0", nil}, - {"1e-20", "1e-20", nil}, - {"625e-3", "0.625", nil}, + {"1.234e", "", nil}, // error + {"1i", "1", nil}, // pass + {"1", "1", nil}, + {"1e23", "1e+23", nil}, + {"1E23", "1e+23", nil}, + {"100000000000000000000000", "1e+23", nil}, + {"1e-100", "1e-100", nil}, + {"123456700", "1.234567e+08", nil}, + {"99999999999999974834176", "9.999999999999997e+22", nil}, + {"100000000000000000000001", "1.0000000000000001e+23", nil}, + {"100000000000000008388608", "1.0000000000000001e+23", nil}, + {"100000000000000016777215", "1.0000000000000001e+23", nil}, + {"100000000000000016777216", "1.0000000000000003e+23", nil}, + {"-1", "-1", nil}, + {"-0.1", "-0.1", nil}, + {"-0", "-0", nil}, + {"1e-20", "1e-20", nil}, + {"625e-3", "0.625", nil}, - // zeros - {"0", "0", nil}, - {"0e0", "0", nil}, - {"-0e0", "-0", nil}, - {"0e-0", "0", nil}, - {"-0e-0", "-0", nil}, - {"0e+0", "0", nil}, - {"-0e+0", "-0", nil}, - {"0e+01234567890123456789", "0", nil}, - {"0.00e-01234567890123456789", "0", nil}, - {"-0e+01234567890123456789", "-0", nil}, - {"-0.00e-01234567890123456789", "-0", nil}, + // zeros + {"0", "0", nil}, + {"0e0", "0", nil}, + {"-0e0", "-0", nil}, + {"0e-0", "0", nil}, + {"-0e-0", "-0", nil}, + {"0e+0", "0", nil}, + {"-0e+0", "-0", nil}, + {"0e+01234567890123456789", "0", nil}, + {"0.00e-01234567890123456789", "0", nil}, + {"-0e+01234567890123456789", "-0", nil}, + {"-0.00e-01234567890123456789", "-0", nil}, - {"0e291", "0", nil}, // issue 15364 - {"0e292", "0", nil}, // issue 15364 - {"0e347", "0", nil}, // issue 15364 - {"0e348", "0", nil}, // issue 15364 - {"-0e291", "-0", nil}, - {"-0e292", "-0", nil}, - {"-0e347", "-0", nil}, - {"-0e348", "-0", nil}, + {"0e291", "0", nil}, // issue 15364 + {"0e292", "0", nil}, // issue 15364 + {"0e347", "0", nil}, // issue 15364 + {"0e348", "0", nil}, // issue 15364 + {"-0e291", "-0", nil}, + {"-0e292", "-0", nil}, + {"-0e347", "-0", nil}, + {"-0e348", "-0", nil}, - // largest float64 - {"1.7976931348623157e308", "1.7976931348623157e+308", nil}, - {"-1.7976931348623157e308", "-1.7976931348623157e+308", nil}, + // largest float64 + {"1.7976931348623157e308", "1.7976931348623157e+308", nil}, + {"-1.7976931348623157e308", "-1.7976931348623157e+308", nil}, - // the border is ...158079 - // borderline - okay - {"1.7976931348623158e308", "1.7976931348623157e+308", nil}, - {"-1.7976931348623158e308", "-1.7976931348623157e+308", nil}, + // the border is ...158079 + // borderline - okay + {"1.7976931348623158e308", "1.7976931348623157e+308", nil}, + {"-1.7976931348623158e308", "-1.7976931348623157e+308", nil}, - // a little too large - {"1e308", "1e+308", nil}, + // a little too large + {"1e308", "1e+308", nil}, - // denormalized - {"1e-305", "1e-305", nil}, - {"1e-306", "1e-306", nil}, - {"1e-307", "1e-307", nil}, - {"1e-308", "1e-308", nil}, - {"1e-309", "1e-309", nil}, - {"1e-310", "1e-310", nil}, - {"1e-322", "1e-322", nil}, - // smallest denormal - {"5e-324", "5e-324", nil}, - {"4e-324", "5e-324", nil}, - {"3e-324", "5e-324", nil}, - // too small - {"2e-324", "0", nil}, - // way too small - {"1e-350", "0", nil}, - {"1e-400000", "0", nil}, + // denormalized + {"1e-305", "1e-305", nil}, + {"1e-306", "1e-306", nil}, + {"1e-307", "1e-307", nil}, + {"1e-308", "1e-308", nil}, + {"1e-309", "1e-309", nil}, + {"1e-310", "1e-310", nil}, + {"1e-322", "1e-322", nil}, + // smallest denormal + {"5e-324", "5e-324", nil}, + {"4e-324", "5e-324", nil}, + {"3e-324", "5e-324", nil}, + // too small + {"2e-324", "0", nil}, + // way too small + {"1e-350", "0", nil}, + {"1e-400000", "0", nil}, - // try to overflow exponent - {"1e-4294967296", "0", nil}, - {"1e-18446744073709551616", "0", nil}, + // try to overflow exponent + {"1e-4294967296", "0", nil}, + {"1e-18446744073709551616", "0", nil}, - // https://www.exploringbinary.com/java-hangs-when-converting-2-2250738585072012e-308/ - {"2.2250738585072012e-308", "2.2250738585072014e-308", nil}, - // https://www.exploringbinary.com/php-hangs-on-numeric-value-2-2250738585072011e-308/ - {"2.2250738585072011e-308", "2.225073858507201e-308", nil}, + // https://www.exploringbinary.com/java-hangs-when-converting-2-2250738585072012e-308/ + {"2.2250738585072012e-308", "2.2250738585072014e-308", nil}, + // https://www.exploringbinary.com/php-hangs-on-numeric-value-2-2250738585072011e-308/ + {"2.2250738585072011e-308", "2.225073858507201e-308", nil}, - // A very large number (initially wrongly parsed by the fast algorithm). - {"4.630813248087435e+307", "4.630813248087435e+307", nil}, + // A very large number (initially wrongly parsed by the fast algorithm). + {"4.630813248087435e+307", "4.630813248087435e+307", nil}, - // A different kind of very large number. - {"22.222222222222222", "22.22222222222222", nil}, - {"2." + strings.Repeat("2", 800) + "e+1", "22.22222222222222", nil}, + // A different kind of very large number. + {"22.222222222222222", "22.22222222222222", nil}, + {"2." + strings.Repeat("2", 800) + "e+1", "22.22222222222222", nil}, - // Exactly halfway between 1 and math.Nextafter(1, 2). - // Round to even (down). - {"1.00000000000000011102230246251565404236316680908203125", "1", nil}, - // Slightly lower; still round down. - {"1.00000000000000011102230246251565404236316680908203124", "1", nil}, - // Slightly higher; round up. - {"1.00000000000000011102230246251565404236316680908203126", "1.0000000000000002", nil}, - // Slightly higher, but you have to read all the way to the end. - {"1.00000000000000011102230246251565404236316680908203125" + strings.Repeat("0", 10000) + "1", "1.0000000000000002", nil}, + // Exactly halfway between 1 and math.Nextafter(1, 2). + // Round to even (down). + {"1.00000000000000011102230246251565404236316680908203125", "1", nil}, + // Slightly lower; still round down. + {"1.00000000000000011102230246251565404236316680908203124", "1", nil}, + // Slightly higher; round up. + {"1.00000000000000011102230246251565404236316680908203126", "1.0000000000000002", nil}, + // Slightly higher, but you have to read all the way to the end. + {"1.00000000000000011102230246251565404236316680908203125" + strings.Repeat("0", 10000) + "1", "1.0000000000000002", nil}, - // Halfway between x := math.Nextafter(1, 2) and math.Nextafter(x, 2) - // Round to even (up). - {"1.00000000000000033306690738754696212708950042724609375", "1.0000000000000004", nil}, + // Halfway between x := math.Nextafter(1, 2) and math.Nextafter(x, 2) + // Round to even (up). + {"1.00000000000000033306690738754696212708950042724609375", "1.0000000000000004", nil}, - // Halfway between 1090544144181609278303144771584 and 1090544144181609419040633126912 - // (15497564393479157p+46, should round to even 15497564393479156p+46, issue 36657) - {"1090544144181609348671888949248", "1.0905441441816093e+30", nil}, + // Halfway between 1090544144181609278303144771584 and 1090544144181609419040633126912 + // (15497564393479157p+46, should round to even 15497564393479156p+46, issue 36657) + {"1090544144181609348671888949248", "1.0905441441816093e+30", nil}, - // Corner case between int64 and float64 for the input - {"9223372036854775807", "9223372036854775807", nil}, // max int64: (1 << 63) - 1 - {"9223372036854775808", "9223372036854775808", nil}, - {"-9223372036854775808", "-9223372036854775808", nil}, // min int64: 1 << 63 - {"-9223372036854775809", "-9223372036854775809", nil}, + // Corner case between int64 and float64 for the input + {"9223372036854775807", "9223372036854775807", nil}, // max int64: (1 << 63) - 1 + {"9223372036854775808", "9223372036854775808", nil}, + {"-9223372036854775808", "-9223372036854775808", nil}, // min int64: 1 << 63 + {"-9223372036854775809", "-9223372036854775809", nil}, } func TestDecodeFloat(t *testing.T) { - for i, tt := range atoftests { - // default float64 - var sonicout, stdout float64 - sonicerr := decoder.NewDecoder(tt.in).Decode(&sonicout) - stderr := json.NewDecoder(strings.NewReader(tt.in)).Decode(&stdout) - if !reflect.DeepEqual(sonicout, stdout) { - t.Fatalf("Test %d, %#v\ngot:\n %#v\nexp:\n %#v\n", i, tt.in, sonicout, stdout) - } - if !reflect.DeepEqual(sonicerr == nil, stderr == nil) { - t.Fatalf("Test %d, %#v\ngot:\n %#v\nexp:\n %#v\n", i, tt.in, sonicerr, stderr) - } - } + for i, tt := range atoftests { + // default float64 + var sonicout, stdout float64 + sonicerr := decoder.NewDecoder(tt.in).Decode(&sonicout) + stderr := json.NewDecoder(strings.NewReader(tt.in)).Decode(&stdout) + if !reflect.DeepEqual(sonicout, stdout) { + t.Fatalf("Test %d, %#v\ngot:\n %#v\nexp:\n %#v\n", i, tt.in, sonicout, stdout) + } + if !reflect.DeepEqual(sonicerr == nil, stderr == nil) { + t.Fatalf("Test %d, %#v\ngot:\n %#v\nexp:\n %#v\n", i, tt.in, sonicerr, stderr) + } + } } diff --git a/decode_test.go b/decode_test.go index 52f8b63..6bed091 100644 --- a/decode_test.go +++ b/decode_test.go @@ -1,3 +1,5 @@ +// +build amd64 + /* * Copyright 2021 ByteDance Inc. * @@ -17,84 +19,84 @@ package sonic import ( - `bytes` - `encoding` - `encoding/json` - `errors` - `fmt` - `image` - `math` - `math/big` - `math/rand` - `net` - `reflect` - `strconv` - `strings` - `testing` - `time` - `unsafe` + `bytes` + `encoding` + `encoding/json` + `errors` + `fmt` + `image` + `math` + `math/big` + `math/rand` + `net` + `reflect` + `strconv` + `strings` + `testing` + `time` + `unsafe` - `github.com/bytedance/sonic/decoder` + `github.com/bytedance/sonic/decoder` ) type T struct { - X string - Y int - Z int `json:"-"` + X string + Y int + Z int `json:"-"` } type U struct { - Alphabet string `json:"alpha"` + Alphabet string `json:"alpha"` } type V struct { - F1 interface{} - F2 int32 - F3 json.Number - F4 *VOuter + F1 interface{} + F2 int32 + F3 json.Number + F4 *VOuter } type VOuter struct { - V V + V V } type W struct { - S SS + S SS } type P struct { - PP PP + PP PP } type PP struct { - T T - Ts []T + T T + Ts []T } type SS string func (*SS) UnmarshalJSON(_ []byte) error { - return &json.UnmarshalTypeError{Value: "number", Type: reflect.TypeOf(SS(""))} + return &json.UnmarshalTypeError{Value: "number", Type: reflect.TypeOf(SS(""))} } // ifaceNumAsFloat64/ifaceNumAsNumber are used to test unmarshaling with and // without UseNumber var ifaceNumAsFloat64 = map[string]interface{}{ - "k1": float64(1), - "k2": "s", - "k3": []interface{}{float64(1), 2.0, 3e-3}, - "k4": map[string]interface{}{"kk1": "s", "kk2": float64(2)}, + "k1": float64(1), + "k2": "s", + "k3": []interface{}{float64(1), 2.0, 3e-3}, + "k4": map[string]interface{}{"kk1": "s", "kk2": float64(2)}, } var ifaceNumAsNumber = map[string]interface{}{ - "k1": json.Number("1"), - "k2": "s", - "k3": []interface{}{json.Number("1"), json.Number("2.0"), json.Number("3e-3")}, - "k4": map[string]interface{}{"kk1": "s", "kk2": json.Number("2")}, + "k1": json.Number("1"), + "k2": "s", + "k3": []interface{}{json.Number("1"), json.Number("2.0"), json.Number("3e-3")}, + "k4": map[string]interface{}{"kk1": "s", "kk2": json.Number("2")}, } type tx struct { - x int + x int } type u8 uint8 @@ -102,109 +104,109 @@ type u8 uint8 // A type that can unmarshal itself. type unmarshaler struct { - T bool + T bool } func (u *unmarshaler) UnmarshalJSON(_ []byte) error { - *u = unmarshaler{true} // All we need to see that UnmarshalJSON is called. - return nil + *u = unmarshaler{true} // All we need to see that UnmarshalJSON is called. + return nil } type ustruct struct { - M unmarshaler + M unmarshaler } type unmarshalerText struct { - A, B string + A, B string } // needed for re-marshaling tests func (u unmarshalerText) MarshalText() ([]byte, error) { - return []byte(u.A + ":" + u.B), nil + return []byte(u.A + ":" + u.B), nil } func (u *unmarshalerText) UnmarshalText(b []byte) error { - pos := bytes.IndexByte(b, ':') - if pos == -1 { - return errors.New("missing separator") - } - u.A, u.B = string(b[:pos]), string(b[pos+1:]) - return nil + pos := bytes.IndexByte(b, ':') + if pos == -1 { + return errors.New("missing separator") + } + u.A, u.B = string(b[:pos]), string(b[pos+1:]) + return nil } var _ encoding.TextUnmarshaler = (*unmarshalerText)(nil) type ustructText struct { - M unmarshalerText + M unmarshalerText } // u8marshal is an integer type that can marshal/unmarshal itself. type u8marshal uint8 func (u8 u8marshal) MarshalText() ([]byte, error) { - return []byte(fmt.Sprintf("u%d", u8)), nil + return []byte(fmt.Sprintf("u%d", u8)), nil } var errMissingU8Prefix = errors.New("missing 'u' prefix") func (u8 *u8marshal) UnmarshalText(b []byte) error { - if !bytes.HasPrefix(b, []byte{'u'}) { - return errMissingU8Prefix - } - n, err := strconv.Atoi(string(b[1:])) - if err != nil { - return err - } - *u8 = u8marshal(n) - return nil + if !bytes.HasPrefix(b, []byte{'u'}) { + return errMissingU8Prefix + } + n, err := strconv.Atoi(string(b[1:])) + if err != nil { + return err + } + *u8 = u8marshal(n) + return nil } var _ encoding.TextUnmarshaler = (*u8marshal)(nil) var ( - umtrue = unmarshaler{true} - umslice = []unmarshaler{{true}} - umstruct = ustruct{unmarshaler{true}} + umtrue = unmarshaler{true} + umslice = []unmarshaler{{true}} + umstruct = ustruct{unmarshaler{true}} - umtrueXY = unmarshalerText{"x", "y"} - umsliceXY = []unmarshalerText{{"x", "y"}} - umstructXY = ustructText{unmarshalerText{"x", "y"}} + umtrueXY = unmarshalerText{"x", "y"} + umsliceXY = []unmarshalerText{{"x", "y"}} + umstructXY = ustructText{unmarshalerText{"x", "y"}} - ummapXY = map[unmarshalerText]bool{{"x", "y"}: true} + ummapXY = map[unmarshalerText]bool{{"x", "y"}: true} ) // Test data structures for anonymous fields. type Point struct { - Z int + Z int } type Top struct { - Level0 int - Embed0 - *Embed0a - *Embed0b `json:"e,omitempty"` // treated as named - Embed0c `json:"-"` // ignored - Loop - Embed0p // has Point with X, Y, used - Embed0q // has Point with Z, used - embed // contains exported field + Level0 int + Embed0 + *Embed0a + *Embed0b `json:"e,omitempty"` // treated as named + Embed0c `json:"-"` // ignored + Loop + Embed0p // has Point with X, Y, used + Embed0q // has Point with Z, used + embed // contains exported field } type Embed0 struct { - Level1a int // overridden by Embed0a's Level1a with json tag - Level1b int // used because Embed0a's Level1b is renamed - Level1c int // used because Embed0a's Level1c is ignored - Level1d int // annihilated by Embed0a's Level1d - Level1e int `json:"x"` // annihilated by Embed0a.Level1e + Level1a int // overridden by Embed0a's Level1a with json tag + Level1b int // used because Embed0a's Level1b is renamed + Level1c int // used because Embed0a's Level1c is ignored + Level1d int // annihilated by Embed0a's Level1d + Level1e int `json:"x"` // annihilated by Embed0a.Level1e } type Embed0a struct { - Level1a int `json:"Level1a,omitempty"` - Level1b int `json:"LEVEL1B,omitempty"` - Level1c int `json:"-"` - Level1d int // annihilated by Embed0's Level1d - Level1f int `json:"x"` // annihilated by Embed0's Level1e + Level1a int `json:"Level1a,omitempty"` + Level1b int `json:"LEVEL1B,omitempty"` + Level1c int `json:"-"` + Level1d int // annihilated by Embed0's Level1d + Level1f int `json:"x"` // annihilated by Embed0's Level1e } type Embed0b Embed0 @@ -212,1838 +214,1838 @@ type Embed0b Embed0 type Embed0c Embed0 type Embed0p struct { - image.Point + image.Point } type Embed0q struct { - Point + Point } type embed struct { - Q int + Q int } type Loop struct { - Loop1 int `json:",omitempty"` - Loop2 int `json:",omitempty"` - *Loop + Loop1 int `json:",omitempty"` + Loop2 int `json:",omitempty"` + *Loop } // From reflect test: // The X in S6 and S7 annihilate, but they also block the X in S8.S9. type S5 struct { - S6 - S7 - S8 + S6 + S7 + S8 } type S6 struct { - X int + X int } type S7 S6 type S8 struct { - S9 + S9 } type S9 struct { - X int - Y int + X int + Y int } // From reflect test: // The X in S11.S6 and S12.S6 annihilate, but they also block the X in S13.S8.S9. type S10 struct { - S11 - S12 - S13 + S11 + S12 + S13 } type S11 struct { - S6 + S6 } type S12 struct { - S6 + S6 } type S13 struct { - S8 + S8 } type Ambig struct { - // Given "hello", the first match should win. - First int `json:"HELLO"` - Second int `json:"Hello"` + // Given "hello", the first match should win. + First int `json:"HELLO"` + Second int `json:"Hello"` } type XYZ struct { - X interface{} - Y interface{} - Z interface{} + X interface{} + Y interface{} + Z interface{} } type byteWithMarshalJSON byte func (b byteWithMarshalJSON) MarshalJSON() ([]byte, error) { - return []byte(fmt.Sprintf(`"Z%.2x"`, byte(b))), nil + return []byte(fmt.Sprintf(`"Z%.2x"`, byte(b))), nil } func (b *byteWithMarshalJSON) UnmarshalJSON(data []byte) error { - if len(data) != 5 || data[0] != '"' || data[1] != 'Z' || data[4] != '"' { - return fmt.Errorf("bad quoted string") - } - i, err := strconv.ParseInt(string(data[2:4]), 16, 8) - if err != nil { - return fmt.Errorf("bad hex") - } - *b = byteWithMarshalJSON(i) - return nil + if len(data) != 5 || data[0] != '"' || data[1] != 'Z' || data[4] != '"' { + return fmt.Errorf("bad quoted string") + } + i, err := strconv.ParseInt(string(data[2:4]), 16, 8) + if err != nil { + return fmt.Errorf("bad hex") + } + *b = byteWithMarshalJSON(i) + return nil } type byteWithPtrMarshalJSON byte func (b *byteWithPtrMarshalJSON) MarshalJSON() ([]byte, error) { - return byteWithMarshalJSON(*b).MarshalJSON() + return byteWithMarshalJSON(*b).MarshalJSON() } func (b *byteWithPtrMarshalJSON) UnmarshalJSON(data []byte) error { - return (*byteWithMarshalJSON)(b).UnmarshalJSON(data) + return (*byteWithMarshalJSON)(b).UnmarshalJSON(data) } type byteWithMarshalText byte func (b byteWithMarshalText) MarshalText() ([]byte, error) { - return []byte(fmt.Sprintf(`Z%.2x`, byte(b))), nil + return []byte(fmt.Sprintf(`Z%.2x`, byte(b))), nil } func (b *byteWithMarshalText) UnmarshalText(data []byte) error { - if len(data) != 3 || data[0] != 'Z' { - return fmt.Errorf("bad quoted string") - } - i, err := strconv.ParseInt(string(data[1:3]), 16, 8) - if err != nil { - return fmt.Errorf("bad hex") - } - *b = byteWithMarshalText(i) - return nil + if len(data) != 3 || data[0] != 'Z' { + return fmt.Errorf("bad quoted string") + } + i, err := strconv.ParseInt(string(data[1:3]), 16, 8) + if err != nil { + return fmt.Errorf("bad hex") + } + *b = byteWithMarshalText(i) + return nil } type byteWithPtrMarshalText byte func (b *byteWithPtrMarshalText) MarshalText() ([]byte, error) { - return byteWithMarshalText(*b).MarshalText() + return byteWithMarshalText(*b).MarshalText() } func (b *byteWithPtrMarshalText) UnmarshalText(data []byte) error { - return (*byteWithMarshalText)(b).UnmarshalText(data) + return (*byteWithMarshalText)(b).UnmarshalText(data) } type intWithMarshalJSON int func (b intWithMarshalJSON) MarshalJSON() ([]byte, error) { - return []byte(fmt.Sprintf(`"Z%.2x"`, int(b))), nil + return []byte(fmt.Sprintf(`"Z%.2x"`, int(b))), nil } func (b *intWithMarshalJSON) UnmarshalJSON(data []byte) error { - if len(data) != 5 || data[0] != '"' || data[1] != 'Z' || data[4] != '"' { - return fmt.Errorf("bad quoted string") - } - i, err := strconv.ParseInt(string(data[2:4]), 16, 8) - if err != nil { - return fmt.Errorf("bad hex") - } - *b = intWithMarshalJSON(i) - return nil + if len(data) != 5 || data[0] != '"' || data[1] != 'Z' || data[4] != '"' { + return fmt.Errorf("bad quoted string") + } + i, err := strconv.ParseInt(string(data[2:4]), 16, 8) + if err != nil { + return fmt.Errorf("bad hex") + } + *b = intWithMarshalJSON(i) + return nil } type intWithPtrMarshalJSON int func (b *intWithPtrMarshalJSON) MarshalJSON() ([]byte, error) { - return intWithMarshalJSON(*b).MarshalJSON() + return intWithMarshalJSON(*b).MarshalJSON() } func (b *intWithPtrMarshalJSON) UnmarshalJSON(data []byte) error { - return (*intWithMarshalJSON)(b).UnmarshalJSON(data) + return (*intWithMarshalJSON)(b).UnmarshalJSON(data) } type intWithMarshalText int func (b intWithMarshalText) MarshalText() ([]byte, error) { - return []byte(fmt.Sprintf(`Z%.2x`, int(b))), nil + return []byte(fmt.Sprintf(`Z%.2x`, int(b))), nil } func (b *intWithMarshalText) UnmarshalText(data []byte) error { - if len(data) != 3 || data[0] != 'Z' { - return fmt.Errorf("bad quoted string") - } - i, err := strconv.ParseInt(string(data[1:3]), 16, 8) - if err != nil { - return fmt.Errorf("bad hex") - } - *b = intWithMarshalText(i) - return nil + if len(data) != 3 || data[0] != 'Z' { + return fmt.Errorf("bad quoted string") + } + i, err := strconv.ParseInt(string(data[1:3]), 16, 8) + if err != nil { + return fmt.Errorf("bad hex") + } + *b = intWithMarshalText(i) + return nil } type intWithPtrMarshalText int func (b *intWithPtrMarshalText) MarshalText() ([]byte, error) { - return intWithMarshalText(*b).MarshalText() + return intWithMarshalText(*b).MarshalText() } func (b *intWithPtrMarshalText) UnmarshalText(data []byte) error { - return (*intWithMarshalText)(b).UnmarshalText(data) + return (*intWithMarshalText)(b).UnmarshalText(data) } type mapStringToStringData struct { - Data map[string]string `json:"data"` + Data map[string]string `json:"data"` } type unmarshalTest struct { - in string - ptr interface{} // new(type) - out interface{} - err error - useNumber bool - golden bool - disallowUnknownFields bool + in string + ptr interface{} // new(type) + out interface{} + err error + useNumber bool + golden bool + disallowUnknownFields bool } type B struct { - B bool `json:",string"` + B bool `json:",string"` } type DoublePtr struct { - I **int - J **int + I **int + J **int } type JsonSyntaxError struct { - Msg string - Offset int64 + Msg string + Offset int64 } func (self *JsonSyntaxError) err() *json.SyntaxError { - return (*json.SyntaxError)(unsafe.Pointer(self)) + return (*json.SyntaxError)(unsafe.Pointer(self)) } var unmarshalTests = []unmarshalTest{ - // basic types - {in: `true`, ptr: new(bool), out: true}, - {in: `1`, ptr: new(int), out: 1}, - {in: `1.2`, ptr: new(float64), out: 1.2}, - {in: `-5`, ptr: new(int16), out: int16(-5)}, - {in: `2`, ptr: new(json.Number), out: json.Number("2"), useNumber: true}, - {in: `2`, ptr: new(json.Number), out: json.Number("2")}, - {in: `2`, ptr: new(interface{}), out: 2.0}, - {in: `2`, ptr: new(interface{}), out: json.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: `{"X": [1,2,3], "Y": 4}`, ptr: new(T), out: T{Y: 4}, err: &json.UnmarshalTypeError{Value: "array", Type: reflect.TypeOf(""), Offset: 7, Struct: "T", Field: "X"}}, - {in: `{"X": 23}`, ptr: new(T), out: T{}, err: &json.UnmarshalTypeError{Value: "number", Type: reflect.TypeOf(""), Offset: 8, Struct: "T", Field: "X"}}, - {in: `{"x": 1}`, ptr: new(tx), out: tx{}}, - {in: `{"x": 1}`, ptr: new(tx), err: fmt.Errorf("json: unknown field \"x\""), disallowUnknownFields: true}, - {in: `{"S": 23}`, ptr: new(W), out: W{}, err: &json.UnmarshalTypeError{Value: "number", Type: reflect.TypeOf(SS("")), Struct: "W", Field: "S"}}, - {in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: float64(1), F2: int32(2), F3: json.Number("3")}}, - {in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: json.Number("1"), F2: int32(2), F3: json.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}, + // basic types + {in: `true`, ptr: new(bool), out: true}, + {in: `1`, ptr: new(int), out: 1}, + {in: `1.2`, ptr: new(float64), out: 1.2}, + {in: `-5`, ptr: new(int16), out: int16(-5)}, + {in: `2`, ptr: new(json.Number), out: json.Number("2"), useNumber: true}, + {in: `2`, ptr: new(json.Number), out: json.Number("2")}, + {in: `2`, ptr: new(interface{}), out: 2.0}, + {in: `2`, ptr: new(interface{}), out: json.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: `{"X": [1,2,3], "Y": 4}`, ptr: new(T), out: T{Y: 4}, err: &json.UnmarshalTypeError{Value: "array", Type: reflect.TypeOf(""), Offset: 7, Struct: "T", Field: "X"}}, + {in: `{"X": 23}`, ptr: new(T), out: T{}, err: &json.UnmarshalTypeError{Value: "number", Type: reflect.TypeOf(""), Offset: 8, Struct: "T", Field: "X"}}, + {in: `{"x": 1}`, ptr: new(tx), out: tx{}}, + {in: `{"x": 1}`, ptr: new(tx), err: fmt.Errorf("json: unknown field \"x\""), disallowUnknownFields: true}, + {in: `{"S": 23}`, ptr: new(W), out: W{}, err: &json.UnmarshalTypeError{Value: "number", Type: reflect.TypeOf(SS("")), Struct: "W", Field: "S"}}, + {in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: float64(1), F2: int32(2), F3: json.Number("3")}}, + {in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: json.Number("1"), F2: int32(2), F3: json.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}, - // raw values with whitespace - {in: "\n true ", ptr: new(bool), out: true}, - {in: "\t 1 ", ptr: new(int), out: 1}, - {in: "\r 1.2 ", ptr: new(float64), out: 1.2}, - {in: "\t -5 \n", ptr: new(int16), out: int16(-5)}, - {in: "\t \"a\\u1234\" \n", ptr: new(string), out: "a\u1234"}, + // raw values with whitespace + {in: "\n true ", ptr: new(bool), out: true}, + {in: "\t 1 ", ptr: new(int), out: 1}, + {in: "\r 1.2 ", ptr: new(float64), out: 1.2}, + {in: "\t -5 \n", ptr: new(int16), out: int16(-5)}, + {in: "\t \"a\\u1234\" \n", ptr: new(string), out: "a\u1234"}, - // Z has a "-" tag. - {in: `{"Y": 1, "Z": 2}`, ptr: new(T), out: T{Y: 1}}, - {in: `{"Y": 1, "Z": 2}`, ptr: new(T), err: fmt.Errorf("json: unknown field \"Z\""), disallowUnknownFields: true}, + // Z has a "-" tag. + {in: `{"Y": 1, "Z": 2}`, ptr: new(T), out: T{Y: 1}}, + {in: `{"Y": 1, "Z": 2}`, ptr: new(T), err: fmt.Errorf("json: unknown field \"Z\""), disallowUnknownFields: true}, - {in: `{"alpha": "abc", "alphabet": "xyz"}`, ptr: new(U), out: U{Alphabet: "abc"}}, - {in: `{"alpha": "abc", "alphabet": "xyz"}`, ptr: new(U), err: fmt.Errorf("json: unknown field \"alphabet\""), disallowUnknownFields: true}, - {in: `{"alpha": "abc"}`, ptr: new(U), out: U{Alphabet: "abc"}}, - {in: `{"alphabet": "xyz"}`, ptr: new(U), out: U{}}, - {in: `{"alphabet": "xyz"}`, ptr: new(U), err: fmt.Errorf("json: unknown field \"alphabet\""), disallowUnknownFields: true}, + {in: `{"alpha": "abc", "alphabet": "xyz"}`, ptr: new(U), out: U{Alphabet: "abc"}}, + {in: `{"alpha": "abc", "alphabet": "xyz"}`, ptr: new(U), err: fmt.Errorf("json: unknown field \"alphabet\""), disallowUnknownFields: true}, + {in: `{"alpha": "abc"}`, ptr: new(U), out: U{Alphabet: "abc"}}, + {in: `{"alphabet": "xyz"}`, ptr: new(U), out: U{}}, + {in: `{"alphabet": "xyz"}`, ptr: new(U), err: fmt.Errorf("json: unknown field \"alphabet\""), disallowUnknownFields: true}, - // syntax errors - {in: `{"X": "foo", "Y"}`, err: (&JsonSyntaxError{"invalid character '}' after object key", 17}).err()}, - {in: `[1, 2, 3+]`, err: (&JsonSyntaxError{"invalid character '+' after array element", 9}).err()}, - {in: `{"X":12x}`, err: (&JsonSyntaxError{"invalid character 'x' after object key:value pair", 8}).err(), useNumber: true}, - {in: `[2, 3`, err: (&JsonSyntaxError{Msg: "unexpected end of JSON input", Offset: 5}).err()}, - {in: `{"F3": -}`, ptr: new(V), out: V{F3: json.Number("-")}, err: (&JsonSyntaxError{Msg: "invalid character '}' in numeric literal", Offset: 9}).err()}, + // syntax errors + {in: `{"X": "foo", "Y"}`, err: (&JsonSyntaxError{"invalid character '}' after object key", 17}).err()}, + {in: `[1, 2, 3+]`, err: (&JsonSyntaxError{"invalid character '+' after array element", 9}).err()}, + {in: `{"X":12x}`, err: (&JsonSyntaxError{"invalid character 'x' after object key:value pair", 8}).err(), useNumber: true}, + {in: `[2, 3`, err: (&JsonSyntaxError{Msg: "unexpected end of JSON input", Offset: 5}).err()}, + {in: `{"F3": -}`, ptr: new(V), out: V{F3: json.Number("-")}, err: (&JsonSyntaxError{Msg: "invalid character '}' in numeric literal", Offset: 9}).err()}, - // raw value errors - {in: "\x01 42", err: (&JsonSyntaxError{"invalid character '\\x01' looking for beginning of value", 1}).err()}, - {in: " 42 \x01", err: (&JsonSyntaxError{"invalid character '\\x01' after top-level value", 5}).err()}, - {in: "\x01 true", err: (&JsonSyntaxError{"invalid character '\\x01' looking for beginning of value", 1}).err()}, - {in: " false \x01", err: (&JsonSyntaxError{"invalid character '\\x01' after top-level value", 8}).err()}, - {in: "\x01 1.2", err: (&JsonSyntaxError{"invalid character '\\x01' looking for beginning of value", 1}).err()}, - {in: " 3.4 \x01", err: (&JsonSyntaxError{"invalid character '\\x01' after top-level value", 6}).err()}, - {in: "\x01 \"string\"", err: (&JsonSyntaxError{"invalid character '\\x01' looking for beginning of value", 1}).err()}, - {in: " \"string\" \x01", err: (&JsonSyntaxError{"invalid character '\\x01' after top-level value", 11}).err()}, + // raw value errors + {in: "\x01 42", err: (&JsonSyntaxError{"invalid character '\\x01' looking for beginning of value", 1}).err()}, + {in: " 42 \x01", err: (&JsonSyntaxError{"invalid character '\\x01' after top-level value", 5}).err()}, + {in: "\x01 true", err: (&JsonSyntaxError{"invalid character '\\x01' looking for beginning of value", 1}).err()}, + {in: " false \x01", err: (&JsonSyntaxError{"invalid character '\\x01' after top-level value", 8}).err()}, + {in: "\x01 1.2", err: (&JsonSyntaxError{"invalid character '\\x01' looking for beginning of value", 1}).err()}, + {in: " 3.4 \x01", err: (&JsonSyntaxError{"invalid character '\\x01' after top-level value", 6}).err()}, + {in: "\x01 \"string\"", err: (&JsonSyntaxError{"invalid character '\\x01' looking for beginning of value", 1}).err()}, + {in: " \"string\" \x01", err: (&JsonSyntaxError{"invalid character '\\x01' after top-level value", 11}).err()}, - // array tests - {in: `[1, 2, 3]`, ptr: new([3]int), out: [3]int{1, 2, 3}}, - {in: `[1, 2, 3]`, ptr: new([1]int), out: [1]int{1}}, - {in: `[1, 2, 3]`, ptr: new([5]int), out: [5]int{1, 2, 3, 0, 0}}, - {in: `[1, 2, 3]`, ptr: new(MustNotUnmarshalJSON), err: errors.New("MustNotUnmarshalJSON was used")}, + // array tests + {in: `[1, 2, 3]`, ptr: new([3]int), out: [3]int{1, 2, 3}}, + {in: `[1, 2, 3]`, ptr: new([1]int), out: [1]int{1}}, + {in: `[1, 2, 3]`, ptr: new([5]int), out: [5]int{1, 2, 3, 0, 0}}, + {in: `[1, 2, 3]`, ptr: new(MustNotUnmarshalJSON), err: errors.New("MustNotUnmarshalJSON was used")}, - // 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)}}, + // 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)}}, - // composite tests - {in: allValueIndent, ptr: new(All), out: allValue}, - {in: allValueCompact, ptr: new(All), out: allValue}, - {in: allValueIndent, ptr: new(*All), out: &allValue}, - {in: allValueCompact, ptr: new(*All), out: &allValue}, - {in: pallValueIndent, ptr: new(All), out: pallValue}, - {in: pallValueCompact, ptr: new(All), out: pallValue}, - {in: pallValueIndent, ptr: new(*All), out: &pallValue}, - {in: pallValueCompact, ptr: new(*All), out: &pallValue}, + // composite tests + {in: allValueIndent, ptr: new(All), out: allValue}, + {in: allValueCompact, ptr: new(All), out: allValue}, + {in: allValueIndent, ptr: new(*All), out: &allValue}, + {in: allValueCompact, ptr: new(*All), out: &allValue}, + {in: pallValueIndent, ptr: new(All), out: pallValue}, + {in: pallValueCompact, ptr: new(All), out: pallValue}, + {in: pallValueIndent, ptr: new(*All), out: &pallValue}, + {in: pallValueCompact, ptr: new(*All), out: &pallValue}, - // unmarshal interface test - {in: `{"T":false}`, ptr: new(unmarshaler), out: umtrue}, // use "false" so test will fail if custom unmarshaler is not called - {in: `{"T":false}`, ptr: new(*unmarshaler), out: &umtrue}, - {in: `[{"T":false}]`, ptr: new([]unmarshaler), out: umslice}, - {in: `[{"T":false}]`, ptr: new(*[]unmarshaler), out: &umslice}, - {in: `{"M":{"T":"x:y"}}`, ptr: new(ustruct), out: umstruct}, + // unmarshal interface test + {in: `{"T":false}`, ptr: new(unmarshaler), out: umtrue}, // use "false" so test will fail if custom unmarshaler is not called + {in: `{"T":false}`, ptr: new(*unmarshaler), out: &umtrue}, + {in: `[{"T":false}]`, ptr: new([]unmarshaler), out: umslice}, + {in: `[{"T":false}]`, ptr: new(*[]unmarshaler), out: &umslice}, + {in: `{"M":{"T":"x:y"}}`, ptr: new(ustruct), out: umstruct}, - // UnmarshalText interface test - {in: `"x:y"`, ptr: new(unmarshalerText), out: umtrueXY}, - {in: `"x:y"`, ptr: new(*unmarshalerText), out: &umtrueXY}, - {in: `["x:y"]`, ptr: new([]unmarshalerText), out: umsliceXY}, - {in: `["x:y"]`, ptr: new(*[]unmarshalerText), out: &umsliceXY}, - {in: `{"M":"x:y"}`, ptr: new(ustructText), out: umstructXY}, + // UnmarshalText interface test + {in: `"x:y"`, ptr: new(unmarshalerText), out: umtrueXY}, + {in: `"x:y"`, ptr: new(*unmarshalerText), out: &umtrueXY}, + {in: `["x:y"]`, ptr: new([]unmarshalerText), out: umsliceXY}, + {in: `["x:y"]`, ptr: new(*[]unmarshalerText), out: &umsliceXY}, + {in: `{"M":"x:y"}`, ptr: new(ustructText), out: umstructXY}, - // integer-keyed map test - { - in: `{"-1":"a","0":"b","1":"c"}`, - ptr: new(map[int]string), - out: map[int]string{-1: "a", 0: "b", 1: "c"}, - }, - { - in: `{"0":"a","10":"c","9":"b"}`, - ptr: new(map[u8]string), - out: map[u8]string{0: "a", 9: "b", 10: "c"}, - }, - { - in: `{"-9223372036854775808":"min","9223372036854775807":"max"}`, - ptr: new(map[int64]string), - out: map[int64]string{math.MinInt64: "min", math.MaxInt64: "max"}, - }, - { - in: `{"18446744073709551615":"max"}`, - ptr: new(map[uint64]string), - out: map[uint64]string{math.MaxUint64: "max"}, - }, - { - in: `{"0":false,"10":true}`, - ptr: new(map[uintptr]bool), - out: map[uintptr]bool{0: false, 10: true}, - }, + // integer-keyed map test + { + in: `{"-1":"a","0":"b","1":"c"}`, + ptr: new(map[int]string), + out: map[int]string{-1: "a", 0: "b", 1: "c"}, + }, + { + in: `{"0":"a","10":"c","9":"b"}`, + ptr: new(map[u8]string), + out: map[u8]string{0: "a", 9: "b", 10: "c"}, + }, + { + in: `{"-9223372036854775808":"min","9223372036854775807":"max"}`, + ptr: new(map[int64]string), + out: map[int64]string{math.MinInt64: "min", math.MaxInt64: "max"}, + }, + { + in: `{"18446744073709551615":"max"}`, + ptr: new(map[uint64]string), + out: map[uint64]string{math.MaxUint64: "max"}, + }, + { + in: `{"0":false,"10":true}`, + ptr: new(map[uintptr]bool), + out: map[uintptr]bool{0: false, 10: true}, + }, - // Check that MarshalText and UnmarshalText take precedence - // over default integer handling in map keys. - { - in: `{"u2":4}`, - ptr: new(map[u8marshal]int), - out: map[u8marshal]int{2: 4}, - }, - { - in: `{"2":4}`, - ptr: new(map[u8marshal]int), - err: errMissingU8Prefix, - }, + // Check that MarshalText and UnmarshalText take precedence + // over default integer handling in map keys. + { + in: `{"u2":4}`, + ptr: new(map[u8marshal]int), + out: map[u8marshal]int{2: 4}, + }, + { + in: `{"2":4}`, + ptr: new(map[u8marshal]int), + err: errMissingU8Prefix, + }, - // integer-keyed map errors - { - in: `{"abc":"abc"}`, - ptr: new(map[int]string), - err: &json.UnmarshalTypeError{Value: "number abc", Type: reflect.TypeOf(0), Offset: 2}, - }, - { - in: `{"256":"abc"}`, - ptr: new(map[uint8]string), - err: &json.UnmarshalTypeError{Value: "number 256", Type: reflect.TypeOf(uint8(0)), Offset: 2}, - }, - { - in: `{"128":"abc"}`, - ptr: new(map[int8]string), - err: &json.UnmarshalTypeError{Value: "number 128", Type: reflect.TypeOf(int8(0)), Offset: 2}, - }, - { - in: `{"-1":"abc"}`, - ptr: new(map[uint8]string), - err: &json.UnmarshalTypeError{Value: "number -1", Type: reflect.TypeOf(uint8(0)), Offset: 2}, - }, - { - in: `{"F":{"a":2,"3":4}}`, - ptr: new(map[string]map[int]int), - err: &json.UnmarshalTypeError{Value: "number a", Type: reflect.TypeOf(0), Offset: 7}, - }, - { - in: `{"F":{"a":2,"3":4}}`, - ptr: new(map[string]map[uint]int), - err: &json.UnmarshalTypeError{Value: "number a", Type: reflect.TypeOf(uint(0)), Offset: 7}, - }, + // integer-keyed map errors + { + in: `{"abc":"abc"}`, + ptr: new(map[int]string), + err: &json.UnmarshalTypeError{Value: "number abc", Type: reflect.TypeOf(0), Offset: 2}, + }, + { + in: `{"256":"abc"}`, + ptr: new(map[uint8]string), + err: &json.UnmarshalTypeError{Value: "number 256", Type: reflect.TypeOf(uint8(0)), Offset: 2}, + }, + { + in: `{"128":"abc"}`, + ptr: new(map[int8]string), + err: &json.UnmarshalTypeError{Value: "number 128", Type: reflect.TypeOf(int8(0)), Offset: 2}, + }, + { + in: `{"-1":"abc"}`, + ptr: new(map[uint8]string), + err: &json.UnmarshalTypeError{Value: "number -1", Type: reflect.TypeOf(uint8(0)), Offset: 2}, + }, + { + in: `{"F":{"a":2,"3":4}}`, + ptr: new(map[string]map[int]int), + err: &json.UnmarshalTypeError{Value: "number a", Type: reflect.TypeOf(0), Offset: 7}, + }, + { + in: `{"F":{"a":2,"3":4}}`, + ptr: new(map[string]map[uint]int), + err: &json.UnmarshalTypeError{Value: "number a", Type: reflect.TypeOf(uint(0)), Offset: 7}, + }, - // Map keys can be encoding.TextUnmarshalers. - {in: `{"x:y":true}`, ptr: new(map[unmarshalerText]bool), out: ummapXY}, - // If multiple values for the same key exists, only the most recent value is used. - {in: `{"x:y":false,"x:y":true}`, ptr: new(map[unmarshalerText]bool), out: ummapXY}, + // Map keys can be encoding.TextUnmarshalers. + {in: `{"x:y":true}`, ptr: new(map[unmarshalerText]bool), out: ummapXY}, + // If multiple values for the same key exists, only the most recent value is used. + {in: `{"x:y":false,"x:y":true}`, ptr: new(map[unmarshalerText]bool), out: ummapXY}, - { - in: `{ - "Level0": 1, - "Level1b": 2, - "Level1c": 3, - "x": 4, - "Level1a": 5, - "LEVEL1B": 6, - "e": { - "Level1a": 8, - "Level1b": 9, - "Level1c": 10, - "Level1d": 11, - "x": 12 - }, - "Loop1": 13, - "Loop2": 14, - "X": 15, - "Y": 16, - "Z": 17, - "Q": 18 - }`, - ptr: new(Top), - out: Top{ - Level0: 1, - Embed0: Embed0{ - Level1b: 2, - Level1c: 3, - }, - Embed0a: &Embed0a{ - Level1a: 5, - Level1b: 6, - }, - Embed0b: &Embed0b{ - Level1a: 8, - Level1b: 9, - Level1c: 10, - Level1d: 11, - Level1e: 12, - }, - Loop: Loop{ - Loop1: 13, - Loop2: 14, - }, - Embed0p: Embed0p{ - Point: image.Point{X: 15, Y: 16}, - }, - Embed0q: Embed0q{ - Point: Point{Z: 17}, - }, - embed: embed{ - Q: 18, - }, - }, - }, - { - in: `{"hello": 1}`, - ptr: new(Ambig), - out: Ambig{First: 1}, - }, + { + in: `{ + "Level0": 1, + "Level1b": 2, + "Level1c": 3, + "x": 4, + "Level1a": 5, + "LEVEL1B": 6, + "e": { + "Level1a": 8, + "Level1b": 9, + "Level1c": 10, + "Level1d": 11, + "x": 12 + }, + "Loop1": 13, + "Loop2": 14, + "X": 15, + "Y": 16, + "Z": 17, + "Q": 18 + }`, + ptr: new(Top), + out: Top{ + Level0: 1, + Embed0: Embed0{ + Level1b: 2, + Level1c: 3, + }, + Embed0a: &Embed0a{ + Level1a: 5, + Level1b: 6, + }, + Embed0b: &Embed0b{ + Level1a: 8, + Level1b: 9, + Level1c: 10, + Level1d: 11, + Level1e: 12, + }, + Loop: Loop{ + Loop1: 13, + Loop2: 14, + }, + Embed0p: Embed0p{ + Point: image.Point{X: 15, Y: 16}, + }, + Embed0q: Embed0q{ + Point: Point{Z: 17}, + }, + embed: embed{ + Q: 18, + }, + }, + }, + { + in: `{"hello": 1}`, + ptr: new(Ambig), + out: Ambig{First: 1}, + }, - { - in: `{"X": 1,"Y":2}`, - ptr: new(S5), - out: S5{S8: S8{S9: S9{Y: 2}}}, - }, - { - in: `{"X": 1,"Y":2}`, - ptr: new(S5), - err: fmt.Errorf("json: unknown field \"X\""), - disallowUnknownFields: true, - }, - { - in: `{"X": 1,"Y":2}`, - ptr: new(S10), - out: S10{S13: S13{S8: S8{S9: S9{Y: 2}}}}, - }, - { - in: `{"X": 1,"Y":2}`, - ptr: new(S10), - err: fmt.Errorf("json: unknown field \"X\""), - disallowUnknownFields: true, - }, - { - in: `{"I": 0, "I": null, "J": null}`, - ptr: new(DoublePtr), - out: DoublePtr{I: nil, J: nil}, - }, + { + in: `{"X": 1,"Y":2}`, + ptr: new(S5), + out: S5{S8: S8{S9: S9{Y: 2}}}, + }, + { + in: `{"X": 1,"Y":2}`, + ptr: new(S5), + err: fmt.Errorf("json: unknown field \"X\""), + disallowUnknownFields: true, + }, + { + in: `{"X": 1,"Y":2}`, + ptr: new(S10), + out: S10{S13: S13{S8: S8{S9: S9{Y: 2}}}}, + }, + { + in: `{"X": 1,"Y":2}`, + ptr: new(S10), + err: fmt.Errorf("json: unknown field \"X\""), + disallowUnknownFields: true, + }, + { + in: `{"I": 0, "I": null, "J": null}`, + ptr: new(DoublePtr), + out: DoublePtr{I: nil, J: nil}, + }, - // invalid UTF-8 is coerced to valid UTF-8. - { - in: "\"hello\xffworld\"", - ptr: new(string), - out: "hello\xffworld", - }, - { - in: "\"hello\xc2\xc2world\"", - ptr: new(string), - out: "hello\xc2\xc2world", - }, - { - in: "\"hello\xc2\xffworld\"", - ptr: new(string), - out: "hello\xc2\xffworld", - }, - { - in: "\"hello\\ud800world\"", - ptr: new(string), - out: "hello\ufffdworld", - }, - { - in: "\"hello\\ud800\\ud800world\"", - ptr: new(string), - out: "hello\ufffd\ufffdworld", - }, - { - in: "\"hello\xed\xa0\x80\xed\xb0\x80world\"", - ptr: new(string), - out: "hello\xed\xa0\x80\xed\xb0\x80world", - }, + // invalid UTF-8 is coerced to valid UTF-8. + { + in: "\"hello\xffworld\"", + ptr: new(string), + out: "hello\xffworld", + }, + { + in: "\"hello\xc2\xc2world\"", + ptr: new(string), + out: "hello\xc2\xc2world", + }, + { + in: "\"hello\xc2\xffworld\"", + ptr: new(string), + out: "hello\xc2\xffworld", + }, + { + in: "\"hello\\ud800world\"", + ptr: new(string), + out: "hello\ufffdworld", + }, + { + in: "\"hello\\ud800\\ud800world\"", + ptr: new(string), + out: "hello\ufffd\ufffdworld", + }, + { + in: "\"hello\xed\xa0\x80\xed\xb0\x80world\"", + ptr: new(string), + out: "hello\xed\xa0\x80\xed\xb0\x80world", + }, - // Used to be issue 8305, but time.Time implements encoding.TextUnmarshaler so this works now. - { - in: `{"2009-11-10T23:00:00Z": "hello world"}`, - ptr: new(map[time.Time]string), - out: map[time.Time]string{time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC): "hello world"}, - }, + // Used to be issue 8305, but time.Time implements encoding.TextUnmarshaler so this works now. + { + in: `{"2009-11-10T23:00:00Z": "hello world"}`, + ptr: new(map[time.Time]string), + out: map[time.Time]string{time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC): "hello world"}, + }, - // issue 8305 - { - in: `{"2009-11-10T23:00:00Z": "hello world"}`, - ptr: new(map[Point]string), - err: &json.UnmarshalTypeError{Value: "object", Type: reflect.TypeOf(map[Point]string{}), Offset: 1}, - }, - { - in: `{"asdf": "hello world"}`, - ptr: new(map[unmarshaler]string), - err: &json.UnmarshalTypeError{Value: "object", Type: reflect.TypeOf(map[unmarshaler]string{}), Offset: 1}, - }, + // issue 8305 + { + in: `{"2009-11-10T23:00:00Z": "hello world"}`, + ptr: new(map[Point]string), + err: &json.UnmarshalTypeError{Value: "object", Type: reflect.TypeOf(map[Point]string{}), Offset: 1}, + }, + { + in: `{"asdf": "hello world"}`, + ptr: new(map[unmarshaler]string), + err: &json.UnmarshalTypeError{Value: "object", Type: reflect.TypeOf(map[unmarshaler]string{}), Offset: 1}, + }, - // related to issue 13783. - // Go 1.7 changed marshaling a slice of typed byte to use the methods on the byte type, - // similar to marshaling a slice of typed int. - // These tests check that, assuming the byte type also has valid decoding methods, - // either the old base64 string encoding or the new per-element encoding can be - // successfully unmarshaled. The custom unmarshalers were accessible in earlier - // versions of Go, even though the custom marshaler was not. - { - in: `"AQID"`, - ptr: new([]byteWithMarshalJSON), - out: []byteWithMarshalJSON{1, 2, 3}, - }, - { - in: `["Z01","Z02","Z03"]`, - ptr: new([]byteWithMarshalJSON), - out: []byteWithMarshalJSON{1, 2, 3}, - golden: true, - }, - { - in: `"AQID"`, - ptr: new([]byteWithMarshalText), - out: []byteWithMarshalText{1, 2, 3}, - }, - { - in: `["Z01","Z02","Z03"]`, - ptr: new([]byteWithMarshalText), - out: []byteWithMarshalText{1, 2, 3}, - golden: true, - }, - { - in: `"AQID"`, - ptr: new([]byteWithPtrMarshalJSON), - out: []byteWithPtrMarshalJSON{1, 2, 3}, - }, - { - in: `["Z01","Z02","Z03"]`, - ptr: new([]byteWithPtrMarshalJSON), - out: []byteWithPtrMarshalJSON{1, 2, 3}, - golden: true, - }, - { - in: `"AQID"`, - ptr: new([]byteWithPtrMarshalText), - out: []byteWithPtrMarshalText{1, 2, 3}, - }, - { - in: `["Z01","Z02","Z03"]`, - ptr: new([]byteWithPtrMarshalText), - out: []byteWithPtrMarshalText{1, 2, 3}, - golden: true, - }, + // related to issue 13783. + // Go 1.7 changed marshaling a slice of typed byte to use the methods on the byte type, + // similar to marshaling a slice of typed int. + // These tests check that, assuming the byte type also has valid decoding methods, + // either the old base64 string encoding or the new per-element encoding can be + // successfully unmarshaled. The custom unmarshalers were accessible in earlier + // versions of Go, even though the custom marshaler was not. + { + in: `"AQID"`, + ptr: new([]byteWithMarshalJSON), + out: []byteWithMarshalJSON{1, 2, 3}, + }, + { + in: `["Z01","Z02","Z03"]`, + ptr: new([]byteWithMarshalJSON), + out: []byteWithMarshalJSON{1, 2, 3}, + golden: true, + }, + { + in: `"AQID"`, + ptr: new([]byteWithMarshalText), + out: []byteWithMarshalText{1, 2, 3}, + }, + { + in: `["Z01","Z02","Z03"]`, + ptr: new([]byteWithMarshalText), + out: []byteWithMarshalText{1, 2, 3}, + golden: true, + }, + { + in: `"AQID"`, + ptr: new([]byteWithPtrMarshalJSON), + out: []byteWithPtrMarshalJSON{1, 2, 3}, + }, + { + in: `["Z01","Z02","Z03"]`, + ptr: new([]byteWithPtrMarshalJSON), + out: []byteWithPtrMarshalJSON{1, 2, 3}, + golden: true, + }, + { + in: `"AQID"`, + ptr: new([]byteWithPtrMarshalText), + out: []byteWithPtrMarshalText{1, 2, 3}, + }, + { + in: `["Z01","Z02","Z03"]`, + ptr: new([]byteWithPtrMarshalText), + out: []byteWithPtrMarshalText{1, 2, 3}, + golden: true, + }, - // ints work with the marshaler but not the base64 []byte case - { - in: `["Z01","Z02","Z03"]`, - ptr: new([]intWithMarshalJSON), - out: []intWithMarshalJSON{1, 2, 3}, - golden: true, - }, - { - in: `["Z01","Z02","Z03"]`, - ptr: new([]intWithMarshalText), - out: []intWithMarshalText{1, 2, 3}, - golden: true, - }, - { - in: `["Z01","Z02","Z03"]`, - ptr: new([]intWithPtrMarshalJSON), - out: []intWithPtrMarshalJSON{1, 2, 3}, - golden: true, - }, - { - in: `["Z01","Z02","Z03"]`, - ptr: new([]intWithPtrMarshalText), - out: []intWithPtrMarshalText{1, 2, 3}, - golden: true, - }, + // ints work with the marshaler but not the base64 []byte case + { + in: `["Z01","Z02","Z03"]`, + ptr: new([]intWithMarshalJSON), + out: []intWithMarshalJSON{1, 2, 3}, + golden: true, + }, + { + in: `["Z01","Z02","Z03"]`, + ptr: new([]intWithMarshalText), + out: []intWithMarshalText{1, 2, 3}, + golden: true, + }, + { + in: `["Z01","Z02","Z03"]`, + ptr: new([]intWithPtrMarshalJSON), + out: []intWithPtrMarshalJSON{1, 2, 3}, + golden: true, + }, + { + in: `["Z01","Z02","Z03"]`, + ptr: new([]intWithPtrMarshalText), + out: []intWithPtrMarshalText{1, 2, 3}, + golden: true, + }, - {in: `0.000001`, ptr: new(float64), out: 0.000001, golden: true}, - {in: `1e-7`, ptr: new(float64), out: 1e-7, golden: true}, - {in: `100000000000000000000`, ptr: new(float64), out: 100000000000000000000.0, golden: true}, - {in: `1e21`, ptr: new(float64), out: 1e21, golden: true}, - {in: `-0.000001`, ptr: new(float64), out: -0.000001, golden: true}, - {in: `-1e-7`, ptr: new(float64), out: -1e-7, golden: true}, - {in: `-100000000000000000000`, ptr: new(float64), out: -100000000000000000000.0, golden: true}, - {in: `-1e21`, ptr: new(float64), out: -1e21, golden: true}, - {in: `999999999999999900000`, ptr: new(float64), out: 999999999999999900000.0, golden: true}, - {in: `9007199254740992`, ptr: new(float64), out: 9007199254740992.0, golden: true}, - {in: `9007199254740993`, ptr: new(float64), out: 9007199254740992.0, golden: false}, + {in: `0.000001`, ptr: new(float64), out: 0.000001, golden: true}, + {in: `1e-7`, ptr: new(float64), out: 1e-7, golden: true}, + {in: `100000000000000000000`, ptr: new(float64), out: 100000000000000000000.0, golden: true}, + {in: `1e21`, ptr: new(float64), out: 1e21, golden: true}, + {in: `-0.000001`, ptr: new(float64), out: -0.000001, golden: true}, + {in: `-1e-7`, ptr: new(float64), out: -1e-7, golden: true}, + {in: `-100000000000000000000`, ptr: new(float64), out: -100000000000000000000.0, golden: true}, + {in: `-1e21`, ptr: new(float64), out: -1e21, golden: true}, + {in: `999999999999999900000`, ptr: new(float64), out: 999999999999999900000.0, golden: true}, + {in: `9007199254740992`, ptr: new(float64), out: 9007199254740992.0, golden: true}, + {in: `9007199254740993`, ptr: new(float64), out: 9007199254740992.0, golden: false}, - { - in: `{"V": {"F2": "hello"}}`, - ptr: new(VOuter), - err: &json.UnmarshalTypeError{ - Value: "string", - Struct: "V", - Field: "V.F2", - Type: reflect.TypeOf(int32(0)), - Offset: 20, - }, - }, - { - in: `{"V": {"F4": {}, "F2": "hello"}}`, - ptr: new(VOuter), - err: &json.UnmarshalTypeError{ - Value: "string", - Struct: "V", - Field: "V.F2", - Type: reflect.TypeOf(int32(0)), - Offset: 30, - }, - }, + { + in: `{"V": {"F2": "hello"}}`, + ptr: new(VOuter), + err: &json.UnmarshalTypeError{ + Value: "string", + Struct: "V", + Field: "V.F2", + Type: reflect.TypeOf(int32(0)), + Offset: 20, + }, + }, + { + in: `{"V": {"F4": {}, "F2": "hello"}}`, + ptr: new(VOuter), + err: &json.UnmarshalTypeError{ + Value: "string", + Struct: "V", + Field: "V.F2", + Type: reflect.TypeOf(int32(0)), + Offset: 30, + }, + }, - // issue 15146. - // invalid inputs in wrongStringTests below. - {in: `{"B":"true"}`, ptr: new(B), out: B{true}, golden: true}, - {in: `{"B":"false"}`, ptr: new(B), out: B{false}, golden: true}, - {in: `{"B": "maybe"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "maybe" into bool`)}, - {in: `{"B": "tru"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "tru" into bool`)}, - {in: `{"B": "False"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "False" into bool`)}, - {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`)}, - {in: `{"B": [2, 3]}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal unquoted value into bool`)}, + // issue 15146. + // invalid inputs in wrongStringTests below. + {in: `{"B":"true"}`, ptr: new(B), out: B{true}, golden: true}, + {in: `{"B":"false"}`, ptr: new(B), out: B{false}, golden: true}, + {in: `{"B": "maybe"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "maybe" into bool`)}, + {in: `{"B": "tru"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "tru" into bool`)}, + {in: `{"B": "False"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "False" into bool`)}, + {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`)}, + {in: `{"B": [2, 3]}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal unquoted value into bool`)}, - // additional tests for disallowUnknownFields - { - in: `{ - "Level0": 1, - "Level1b": 2, - "Level1c": 3, - "x": 4, - "Level1a": 5, - "LEVEL1B": 6, - "e": { - "Level1a": 8, - "Level1b": 9, - "Level1c": 10, - "Level1d": 11, - "x": 12 - }, - "Loop1": 13, - "Loop2": 14, - "X": 15, - "Y": 16, - "Z": 17, - "Q": 18, - "extra": true - }`, - ptr: new(Top), - err: fmt.Errorf("json: unknown field \"extra\""), - disallowUnknownFields: true, - }, - { - in: `{ - "Level0": 1, - "Level1b": 2, - "Level1c": 3, - "x": 4, - "Level1a": 5, - "LEVEL1B": 6, - "e": { - "Level1a": 8, - "Level1b": 9, - "Level1c": 10, - "Level1d": 11, - "x": 12, - "extra": null - }, - "Loop1": 13, - "Loop2": 14, - "X": 15, - "Y": 16, - "Z": 17, - "Q": 18 - }`, - ptr: new(Top), - err: fmt.Errorf("json: unknown field \"extra\""), - disallowUnknownFields: true, - }, - // issue 26444 - // json.UnmarshalTypeError without field & struct values - { - in: `{"data":{"test1": "bob", "test2": 123}}`, - ptr: new(mapStringToStringData), - err: &json.UnmarshalTypeError{Value: "number", Type: reflect.TypeOf(""), Offset: 37, Struct: "mapStringToStringData", Field: "data"}, - }, - { - in: `{"data":{"test1": 123, "test2": "bob"}}`, - ptr: new(mapStringToStringData), - err: &json.UnmarshalTypeError{Value: "number", Type: reflect.TypeOf(""), Offset: 21, Struct: "mapStringToStringData", Field: "data"}, - }, + // additional tests for disallowUnknownFields + { + in: `{ + "Level0": 1, + "Level1b": 2, + "Level1c": 3, + "x": 4, + "Level1a": 5, + "LEVEL1B": 6, + "e": { + "Level1a": 8, + "Level1b": 9, + "Level1c": 10, + "Level1d": 11, + "x": 12 + }, + "Loop1": 13, + "Loop2": 14, + "X": 15, + "Y": 16, + "Z": 17, + "Q": 18, + "extra": true + }`, + ptr: new(Top), + err: fmt.Errorf("json: unknown field \"extra\""), + disallowUnknownFields: true, + }, + { + in: `{ + "Level0": 1, + "Level1b": 2, + "Level1c": 3, + "x": 4, + "Level1a": 5, + "LEVEL1B": 6, + "e": { + "Level1a": 8, + "Level1b": 9, + "Level1c": 10, + "Level1d": 11, + "x": 12, + "extra": null + }, + "Loop1": 13, + "Loop2": 14, + "X": 15, + "Y": 16, + "Z": 17, + "Q": 18 + }`, + ptr: new(Top), + err: fmt.Errorf("json: unknown field \"extra\""), + disallowUnknownFields: true, + }, + // issue 26444 + // json.UnmarshalTypeError without field & struct values + { + in: `{"data":{"test1": "bob", "test2": 123}}`, + ptr: new(mapStringToStringData), + err: &json.UnmarshalTypeError{Value: "number", Type: reflect.TypeOf(""), Offset: 37, Struct: "mapStringToStringData", Field: "data"}, + }, + { + in: `{"data":{"test1": 123, "test2": "bob"}}`, + ptr: new(mapStringToStringData), + err: &json.UnmarshalTypeError{Value: "number", Type: reflect.TypeOf(""), Offset: 21, Struct: "mapStringToStringData", Field: "data"}, + }, - // trying to decode JSON arrays or objects via TextUnmarshaler - { - in: `[1, 2, 3]`, - ptr: new(MustNotUnmarshalText), - err: &json.UnmarshalTypeError{Value: "array", Type: reflect.TypeOf(&MustNotUnmarshalText{}), Offset: 1}, - }, - { - in: `{"foo": "bar"}`, - ptr: new(MustNotUnmarshalText), - err: &json.UnmarshalTypeError{Value: "object", Type: reflect.TypeOf(&MustNotUnmarshalText{}), Offset: 1}, - }, - // #22369 - { - in: `{"PP": {"T": {"Y": "bad-type"}}}`, - ptr: new(P), - err: &json.UnmarshalTypeError{ - Value: "string", - Struct: "T", - Field: "PP.T.Y", - Type: reflect.TypeOf(0), - Offset: 29, - }, - }, - { - in: `{"Ts": [{"Y": 1}, {"Y": 2}, {"Y": "bad-type"}]}`, - ptr: new(PP), - err: &json.UnmarshalTypeError{ - Value: "string", - Struct: "T", - Field: "Ts.Y", - Type: reflect.TypeOf(0), - Offset: 29, - }, - }, - // #14702 - { - in: `invalid`, - ptr: new(json.Number), - err: (&JsonSyntaxError{ - Msg: "invalid character 'i' looking for beginning of value", - Offset: 1, - }).err(), - }, - { - in: `"invalid"`, - ptr: new(json.Number), - err: fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", `"invalid"`), - }, - { - in: `{"A":"invalid"}`, - ptr: new(struct{ A json.Number }), - err: fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", `"invalid"`), - }, - { - in: `{"A":"invalid"}`, - ptr: new(struct { - A json.Number `json:",string"` - }), - err: fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into json.Number", `invalid`), - }, - { - in: `{"A":"invalid"}`, - ptr: new(map[string]json.Number), - err: fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", `"invalid"`), - }, + // trying to decode JSON arrays or objects via TextUnmarshaler + { + in: `[1, 2, 3]`, + ptr: new(MustNotUnmarshalText), + err: &json.UnmarshalTypeError{Value: "array", Type: reflect.TypeOf(&MustNotUnmarshalText{}), Offset: 1}, + }, + { + in: `{"foo": "bar"}`, + ptr: new(MustNotUnmarshalText), + err: &json.UnmarshalTypeError{Value: "object", Type: reflect.TypeOf(&MustNotUnmarshalText{}), Offset: 1}, + }, + // #22369 + { + in: `{"PP": {"T": {"Y": "bad-type"}}}`, + ptr: new(P), + err: &json.UnmarshalTypeError{ + Value: "string", + Struct: "T", + Field: "PP.T.Y", + Type: reflect.TypeOf(0), + Offset: 29, + }, + }, + { + in: `{"Ts": [{"Y": 1}, {"Y": 2}, {"Y": "bad-type"}]}`, + ptr: new(PP), + err: &json.UnmarshalTypeError{ + Value: "string", + Struct: "T", + Field: "Ts.Y", + Type: reflect.TypeOf(0), + Offset: 29, + }, + }, + // #14702 + { + in: `invalid`, + ptr: new(json.Number), + err: (&JsonSyntaxError{ + Msg: "invalid character 'i' looking for beginning of value", + Offset: 1, + }).err(), + }, + { + in: `"invalid"`, + ptr: new(json.Number), + err: fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", `"invalid"`), + }, + { + in: `{"A":"invalid"}`, + ptr: new(struct{ A json.Number }), + err: fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", `"invalid"`), + }, + { + in: `{"A":"invalid"}`, + ptr: new(struct { + A json.Number `json:",string"` + }), + err: fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into json.Number", `invalid`), + }, + { + in: `{"A":"invalid"}`, + ptr: new(map[string]json.Number), + err: fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", `"invalid"`), + }, } func trim(b []byte) []byte { - if len(b) > 20 { - return b[0:20] - } - return b + if len(b) > 20 { + return b[0:20] + } + return 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 - } - t.Errorf("diverge at %d: «%s» vs «%s»", i, trim(a[j:]), trim(b[j:])) - return - } - } + for i := 0; ; i++ { + if i >= len(a) || i >= len(b) || a[i] != b[i] { + j := i - 10 + if j < 0 { + j = 0 + } + t.Errorf("diverge at %d: «%s» vs «%s»", i, trim(a[j:]), trim(b[j:])) + return + } + } } func TestMarshal(t *testing.T) { - b, err := Marshal(allValue) - if err != nil { - t.Fatalf("Marshal allValue: %v", err) - } - if string(b) != allValueCompact { - t.Errorf("Marshal allValueCompact") - diff(t, b, []byte(allValueCompact)) - return - } + b, err := Marshal(allValue) + if err != nil { + t.Fatalf("Marshal allValue: %v", err) + } + if string(b) != allValueCompact { + t.Errorf("Marshal allValueCompact") + diff(t, b, []byte(allValueCompact)) + return + } - b, err = Marshal(pallValue) - if err != nil { - t.Fatalf("Marshal pallValue: %v", err) - } - if string(b) != pallValueCompact { - t.Errorf("Marshal pallValueCompact") - diff(t, b, []byte(pallValueCompact)) - return - } + b, err = Marshal(pallValue) + if err != nil { + t.Fatalf("Marshal pallValue: %v", err) + } + if string(b) != pallValueCompact { + t.Errorf("Marshal pallValueCompact") + diff(t, b, []byte(pallValueCompact)) + return + } } func TestMarshalNumberZeroVal(t *testing.T) { - var n json.Number - out, err := Marshal(n) - if err != nil { - t.Fatal(err) - } - outStr := string(out) - if outStr != "0" { - t.Fatalf("Invalid zero val for json.Number: %q", outStr) - } + var n json.Number + out, err := Marshal(n) + if err != nil { + t.Fatal(err) + } + outStr := string(out) + if outStr != "0" { + t.Fatalf("Invalid zero val for json.Number: %q", outStr) + } } func TestMarshalEmbeds(t *testing.T) { - top := &Top{ - Level0: 1, - Embed0: Embed0{ - Level1b: 2, - Level1c: 3, - }, - Embed0a: &Embed0a{ - Level1a: 5, - Level1b: 6, - }, - Embed0b: &Embed0b{ - Level1a: 8, - Level1b: 9, - Level1c: 10, - Level1d: 11, - Level1e: 12, - }, - Loop: Loop{ - Loop1: 13, - Loop2: 14, - }, - Embed0p: Embed0p{ - Point: image.Point{X: 15, Y: 16}, - }, - Embed0q: Embed0q{ - Point: Point{Z: 17}, - }, - embed: embed{ - Q: 18, - }, - } - b, err := Marshal(top) - if err != nil { - t.Fatal(err) - } - want := "{\"Level0\":1,\"Level1b\":2,\"Level1c\":3,\"Level1a\":5,\"LEVEL1B\":6,\"e\":{\"Level1a\":8,\"Level1b\":9,\"Level1c\":10,\"Level1d\":11,\"x\":12},\"Loop1\":13,\"Loop2\":14,\"X\":15,\"Y\":16,\"Z\":17,\"Q\":18}" - if string(b) != want { - t.Errorf("Wrong marshal result.\n got: %q\nwant: %q", b, want) - } + top := &Top{ + Level0: 1, + Embed0: Embed0{ + Level1b: 2, + Level1c: 3, + }, + Embed0a: &Embed0a{ + Level1a: 5, + Level1b: 6, + }, + Embed0b: &Embed0b{ + Level1a: 8, + Level1b: 9, + Level1c: 10, + Level1d: 11, + Level1e: 12, + }, + Loop: Loop{ + Loop1: 13, + Loop2: 14, + }, + Embed0p: Embed0p{ + Point: image.Point{X: 15, Y: 16}, + }, + Embed0q: Embed0q{ + Point: Point{Z: 17}, + }, + embed: embed{ + Q: 18, + }, + } + b, err := Marshal(top) + if err != nil { + t.Fatal(err) + } + want := "{\"Level0\":1,\"Level1b\":2,\"Level1c\":3,\"Level1a\":5,\"LEVEL1B\":6,\"e\":{\"Level1a\":8,\"Level1b\":9,\"Level1c\":10,\"Level1d\":11,\"x\":12},\"Loop1\":13,\"Loop2\":14,\"X\":15,\"Y\":16,\"Z\":17,\"Q\":18}" + if string(b) != want { + t.Errorf("Wrong marshal result.\n got: %q\nwant: %q", b, want) + } } func TestUnmarshal(t *testing.T) { - for i, tt := range unmarshalTests { - if !json.Valid([]byte(tt.in)) { - continue - } - if tt.ptr == nil { - continue - } + for i, tt := range unmarshalTests { + if !json.Valid([]byte(tt.in)) { + continue + } + if tt.ptr == nil { + continue + } - typ := reflect.TypeOf(tt.ptr) - if typ.Kind() != reflect.Ptr { - t.Errorf("#%d: unmarshalTest.ptr %T is not a pointer type", i, tt.ptr) - continue - } - typ = typ.Elem() + typ := reflect.TypeOf(tt.ptr) + if typ.Kind() != reflect.Ptr { + t.Errorf("#%d: unmarshalTest.ptr %T is not a pointer type", i, tt.ptr) + continue + } + typ = typ.Elem() - // v = new(right-type) - v := reflect.New(typ) + // v = new(right-type) + v := reflect.New(typ) - if !reflect.DeepEqual(tt.ptr, v.Interface()) { - // There's no reason for ptr to point to non-zero data, - // as we decode into new(right-type), so the data is - // discarded. - // This can easily mean tests that silently don't test - // what they should. To test decoding into existing - // data, see TestPrefilled. - t.Errorf("#%d: unmarshalTest.ptr %#v is not a pointer to a zero value", i, tt.ptr) - continue - } + if !reflect.DeepEqual(tt.ptr, v.Interface()) { + // There's no reason for ptr to point to non-zero data, + // as we decode into new(right-type), so the data is + // discarded. + // This can easily mean tests that silently don't test + // what they should. To test decoding into existing + // data, see TestPrefilled. + t.Errorf("#%d: unmarshalTest.ptr %#v is not a pointer to a zero value", i, tt.ptr) + continue + } - dec := decoder.NewDecoder(tt.in) - if tt.useNumber { - dec.UseNumber() - } - if tt.disallowUnknownFields { - dec.DisallowUnknownFields() - } - if err := dec.Decode(v.Interface()); (err == nil) != (tt.err == nil) { - t.Errorf("#%d: %v, want %v", i, err, tt.err) - continue - } else if err != nil { - continue - } - if !reflect.DeepEqual(v.Elem().Interface(), tt.out) { - t.Errorf("#%d: mismatch\nhave: %#+v\nwant: %#+v", i, v.Elem().Interface(), tt.out) - data, _ := Marshal(v.Elem().Interface()) - println(string(data)) - data, _ = Marshal(tt.out) - println(string(data)) - continue - } + dec := decoder.NewDecoder(tt.in) + if tt.useNumber { + dec.UseNumber() + } + if tt.disallowUnknownFields { + dec.DisallowUnknownFields() + } + if err := dec.Decode(v.Interface()); (err == nil) != (tt.err == nil) { + t.Errorf("#%d: %v, want %v", i, err, tt.err) + continue + } else if err != nil { + continue + } + if !reflect.DeepEqual(v.Elem().Interface(), tt.out) { + t.Errorf("#%d: mismatch\nhave: %#+v\nwant: %#+v", i, v.Elem().Interface(), tt.out) + data, _ := Marshal(v.Elem().Interface()) + println(string(data)) + data, _ = Marshal(tt.out) + println(string(data)) + continue + } - // Check round trip also decodes correctly. - if tt.err == nil { - enc, err := Marshal(v.Interface()) - if err != nil { - t.Errorf("#%d: error re-marshaling: %v", i, err) - continue - } - if tt.golden && !bytes.Equal(enc, []byte(tt.in)) { - t.Errorf("#%d: remarshal mismatch:\nhave: %s\nwant: %s", i, enc, []byte(tt.in)) - } - vv := reflect.New(reflect.TypeOf(tt.ptr).Elem()) - dec = decoder.NewDecoder(string(enc)) - if tt.useNumber { - dec.UseNumber() - } - if err := dec.Decode(vv.Interface()); err != nil { - t.Errorf("#%d: error re-unmarshaling %#q: %v", i, enc, err) - continue - } - if !reflect.DeepEqual(v.Elem().Interface(), vv.Elem().Interface()) { - t.Errorf("#%d: mismatch\nhave: %#+v\nwant: %#+v", i, v.Elem().Interface(), vv.Elem().Interface()) - t.Errorf(" In: %q", strings.Map(noSpace, tt.in)) - t.Errorf("Marshal: %q", strings.Map(noSpace, string(enc))) - continue - } - } - } + // Check round trip also decodes correctly. + if tt.err == nil { + enc, err := Marshal(v.Interface()) + if err != nil { + t.Errorf("#%d: error re-marshaling: %v", i, err) + continue + } + if tt.golden && !bytes.Equal(enc, []byte(tt.in)) { + t.Errorf("#%d: remarshal mismatch:\nhave: %s\nwant: %s", i, enc, []byte(tt.in)) + } + vv := reflect.New(reflect.TypeOf(tt.ptr).Elem()) + dec = decoder.NewDecoder(string(enc)) + if tt.useNumber { + dec.UseNumber() + } + if err := dec.Decode(vv.Interface()); err != nil { + t.Errorf("#%d: error re-unmarshaling %#q: %v", i, enc, err) + continue + } + if !reflect.DeepEqual(v.Elem().Interface(), vv.Elem().Interface()) { + t.Errorf("#%d: mismatch\nhave: %#+v\nwant: %#+v", i, v.Elem().Interface(), vv.Elem().Interface()) + t.Errorf(" In: %q", strings.Map(noSpace, tt.in)) + t.Errorf("Marshal: %q", strings.Map(noSpace, string(enc))) + continue + } + } + } } var jsonBig []byte func initBig() { - n := 10000 - if testing.Short() { - n = 100 - } - b, err := Marshal(genValue(n)) - if err != nil { - panic(err) - } - jsonBig = b + n := 10000 + if testing.Short() { + n = 100 + } + b, err := Marshal(genValue(n)) + if err != nil { + panic(err) + } + jsonBig = b } func genValue(n int) interface{} { - if n > 1 { - switch rand.Intn(2) { - case 0: - return genArray(n) - case 1: - return genMap(n) - } - } - switch rand.Intn(3) { - case 0: - return rand.Intn(2) == 0 - case 1: - return float64(rand.Uint64()) / 4294967296 - case 2: - return genString(30) - } - panic("unreachable") + if n > 1 { + switch rand.Intn(2) { + case 0: + return genArray(n) + case 1: + return genMap(n) + } + } + switch rand.Intn(3) { + case 0: + return rand.Intn(2) == 0 + case 1: + return float64(rand.Uint64()) / 4294967296 + case 2: + return genString(30) + } + panic("unreachable") } func genString(stddev float64) string { - n := int(math.Abs(rand.NormFloat64()*stddev + stddev/2)) - c := make([]rune, n) - for i := range c { - f := math.Abs(rand.NormFloat64()*64 + 32) - if f > 0x10ffff { - f = 0x10ffff - } - c[i] = rune(f) - } - return string(c) + n := int(math.Abs(rand.NormFloat64()*stddev + stddev/2)) + c := make([]rune, n) + for i := range c { + f := math.Abs(rand.NormFloat64()*64 + 32) + if f > 0x10ffff { + f = 0x10ffff + } + c[i] = rune(f) + } + return string(c) } func genArray(n int) []interface{} { - f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2))) - if f > n { - f = n - } - if f < 1 { - f = 1 - } - x := make([]interface{}, f) - for i := range x { - x[i] = genValue(((i+1)*n)/f - (i*n)/f) - } - return x + f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2))) + if f > n { + f = n + } + if f < 1 { + f = 1 + } + x := make([]interface{}, f) + for i := range x { + x[i] = genValue(((i+1)*n)/f - (i*n)/f) + } + return x } func genMap(n int) map[string]interface{} { - f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2))) - if f > n { - f = n - } - if n > 0 && f == 0 { - f = 1 - } - x := make(map[string]interface{}) - x[genString(10)] = genValue(n/f) - return x + f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2))) + if f > n { + f = n + } + if n > 0 && f == 0 { + f = 1 + } + x := make(map[string]interface{}) + x[genString(10)] = genValue(n/f) + return x } func TestUnmarshalMarshal(t *testing.T) { - initBig() - var v interface{} - if err := Unmarshal(jsonBig, &v); err != nil { - if e, ok := err.(decoder.SyntaxError); ok { - println(e.Description()) - } - t.Fatalf("Unmarshal: %v", err) - } - b, err := Marshal(v) - if err != nil { - t.Fatalf("Marshal: %v", err) - } - if !bytes.Equal(jsonBig, b) { - t.Errorf("Marshal jsonBig") - diff(t, b, jsonBig) - println(string(b)) - println(string(jsonBig)) - return - } + initBig() + var v interface{} + if err := Unmarshal(jsonBig, &v); err != nil { + if e, ok := err.(decoder.SyntaxError); ok { + println(e.Description()) + } + t.Fatalf("Unmarshal: %v", err) + } + b, err := Marshal(v) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + if !bytes.Equal(jsonBig, b) { + t.Errorf("Marshal jsonBig") + diff(t, b, jsonBig) + println(string(b)) + println(string(jsonBig)) + return + } } var numberTests = []struct { - in string - i int64 - intErr string - f float64 - floatErr string + in string + i int64 + intErr string + f float64 + floatErr string }{ - {in: "-1.23e1", intErr: "strconv.ParseInt: parsing \"-1.23e1\": invalid syntax", f: -1.23e1}, - {in: "-12", i: -12, f: -12.0}, - {in: "1e1000", intErr: "strconv.ParseInt: parsing \"1e1000\": invalid syntax", floatErr: "strconv.ParseFloat: parsing \"1e1000\": value out of range"}, + {in: "-1.23e1", intErr: "strconv.ParseInt: parsing \"-1.23e1\": invalid syntax", f: -1.23e1}, + {in: "-12", i: -12, f: -12.0}, + {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 json.Number func TestNumberAccessors(t *testing.T) { - for _, tt := range numberTests { - n := json.Number(tt.in) - if s := n.String(); s != tt.in { - t.Errorf("json.Number(%q).String() is %q", tt.in, s) - } - if i, err := n.Int64(); err == nil && tt.intErr == "" && i != tt.i { - t.Errorf("json.Number(%q).Int64() is %d", tt.in, i) - } else if (err == nil && tt.intErr != "") || (err != nil && err.Error() != tt.intErr) { - t.Errorf("json.Number(%q).Int64() wanted error %q but got: %v", tt.in, tt.intErr, err) - } - if f, err := n.Float64(); err == nil && tt.floatErr == "" && f != tt.f { - t.Errorf("json.Number(%q).Float64() is %g", tt.in, f) - } else if (err == nil && tt.floatErr != "") || (err != nil && err.Error() != tt.floatErr) { - t.Errorf("json.Number(%q).Float64() wanted error %q but got: %v", tt.in, tt.floatErr, err) - } - } + for _, tt := range numberTests { + n := json.Number(tt.in) + if s := n.String(); s != tt.in { + t.Errorf("json.Number(%q).String() is %q", tt.in, s) + } + if i, err := n.Int64(); err == nil && tt.intErr == "" && i != tt.i { + t.Errorf("json.Number(%q).Int64() is %d", tt.in, i) + } else if (err == nil && tt.intErr != "") || (err != nil && err.Error() != tt.intErr) { + t.Errorf("json.Number(%q).Int64() wanted error %q but got: %v", tt.in, tt.intErr, err) + } + if f, err := n.Float64(); err == nil && tt.floatErr == "" && f != tt.f { + t.Errorf("json.Number(%q).Float64() is %g", tt.in, f) + } else if (err == nil && tt.floatErr != "") || (err != nil && err.Error() != tt.floatErr) { + t.Errorf("json.Number(%q).Float64() wanted error %q but got: %v", tt.in, tt.floatErr, err) + } + } } func TestLargeByteSlice(t *testing.T) { - s0 := make([]byte, 2000) - for i := range s0 { - s0[i] = byte(i) - } - b, err := Marshal(s0) - if err != nil { - t.Fatalf("Marshal: %v", err) - } - var s1 []byte - if err := Unmarshal(b, &s1); err != nil { - t.Fatalf("Unmarshal: %v", err) - } - if !bytes.Equal(s0, s1) { - t.Errorf("Marshal large byte slice") - diff(t, s0, s1) - } + s0 := make([]byte, 2000) + for i := range s0 { + s0[i] = byte(i) + } + b, err := Marshal(s0) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + var s1 []byte + if err := Unmarshal(b, &s1); err != nil { + t.Fatalf("Unmarshal: %v", err) + } + if !bytes.Equal(s0, s1) { + t.Errorf("Marshal large byte slice") + diff(t, s0, s1) + } } type Xint struct { - X int + X int } func TestUnmarshalPtrPtr(t *testing.T) { - var xint Xint - pxint := &xint - if err := Unmarshal([]byte(`{"X":1}`), &pxint); err != nil { - t.Fatalf("Unmarshal: %v", err) - } - if xint.X != 1 { - t.Fatalf("Did not write to xint") - } + var xint Xint + pxint := &xint + if err := Unmarshal([]byte(`{"X":1}`), &pxint); err != nil { + t.Fatalf("Unmarshal: %v", err) + } + if xint.X != 1 { + t.Fatalf("Did not write to xint") + } } func isSpace(c byte) bool { - return c <= ' ' && (c == ' ' || c == '\t' || c == '\r' || c == '\n') + return c <= ' ' && (c == ' ' || c == '\t' || c == '\r' || c == '\n') } func noSpace(c rune) rune { - if isSpace(byte(c)) { // only used for ascii - return -1 - } - return c + if isSpace(byte(c)) { // only used for ascii + return -1 + } + return c } type All struct { - Bool bool - Int int - Int8 int8 - Int16 int16 - Int32 int32 - Int64 int64 - Uint uint - Uint8 uint8 - Uint16 uint16 - Uint32 uint32 - Uint64 uint64 - Uintptr uintptr - Float32 float32 - Float64 float64 + Bool bool + Int int + Int8 int8 + Int16 int16 + Int32 int32 + Int64 int64 + Uint uint + Uint8 uint8 + Uint16 uint16 + Uint32 uint32 + Uint64 uint64 + Uintptr uintptr + Float32 float32 + Float64 float64 - Foo string `json:"bar"` - Foo2 string `json:"bar2,dummyopt"` + Foo string `json:"bar"` + Foo2 string `json:"bar2,dummyopt"` - IntStr int64 `json:",string"` - UintptrStr uintptr `json:",string"` + IntStr int64 `json:",string"` + UintptrStr uintptr `json:",string"` - PBool *bool - PInt *int - PInt8 *int8 - PInt16 *int16 - PInt32 *int32 - PInt64 *int64 - PUint *uint - PUint8 *uint8 - PUint16 *uint16 - PUint32 *uint32 - PUint64 *uint64 - PUintptr *uintptr - PFloat32 *float32 - PFloat64 *float64 + PBool *bool + PInt *int + PInt8 *int8 + PInt16 *int16 + PInt32 *int32 + PInt64 *int64 + PUint *uint + PUint8 *uint8 + PUint16 *uint16 + PUint32 *uint32 + PUint64 *uint64 + PUintptr *uintptr + PFloat32 *float32 + PFloat64 *float64 - String string - PString *string + String string + PString *string - Map map[string]Small - MapP map[string]*Small - MapPNil map[string]*Small - PMap *map[string]Small - PMapP *map[string]*Small - PMapPNil *map[string]*Small + Map map[string]Small + MapP map[string]*Small + MapPNil map[string]*Small + PMap *map[string]Small + PMapP *map[string]*Small + PMapPNil *map[string]*Small - EmptyMap map[string]Small - NilMap map[string]Small + EmptyMap map[string]Small + NilMap map[string]Small - Slice []Small - SliceP []*Small - PSlice *[]Small - PSliceP *[]*Small + Slice []Small + SliceP []*Small + PSlice *[]Small + PSliceP *[]*Small - EmptySlice []Small - NilSlice []Small + EmptySlice []Small + NilSlice []Small - StringSlice []string - ByteSlice []byte + StringSlice []string + ByteSlice []byte - Small Small - PSmall *Small - PPSmall **Small + Small Small + PSmall *Small + PPSmall **Small - Interface interface{} - PInterface *interface{} + Interface interface{} + PInterface *interface{} - unexported int + unexported int } type Small struct { - Tag string + Tag string } var allValue = All{ - Bool: true, - Int: 2, - Int8: 3, - Int16: 4, - Int32: 5, - Int64: 6, - Uint: 7, - Uint8: 8, - Uint16: 9, - Uint32: 10, - Uint64: 11, - Uintptr: 12, - Float32: 14.25, - Float64: 15.25, - Foo: "foo", - Foo2: "foo2", - IntStr: 42, - UintptrStr: 44, - String: "16", - Map: map[string]Small{ - "17": {Tag: "tag17"}, - }, - MapP: map[string]*Small{ - "18": {Tag: "tag19"}, - }, - MapPNil: map[string]*Small{ - "19": nil, - }, - EmptyMap: map[string]Small{}, - Slice: []Small{{Tag: "tag20"}, {Tag: "tag21"}}, - SliceP: []*Small{{Tag: "tag22"}, nil, {Tag: "tag23"}}, - EmptySlice: []Small{}, - StringSlice: []string{"str24", "str25", "str26"}, - ByteSlice: []byte{27, 28, 29}, - Small: Small{Tag: "tag30"}, - PSmall: &Small{Tag: "tag31"}, - Interface: 5.2, + Bool: true, + Int: 2, + Int8: 3, + Int16: 4, + Int32: 5, + Int64: 6, + Uint: 7, + Uint8: 8, + Uint16: 9, + Uint32: 10, + Uint64: 11, + Uintptr: 12, + Float32: 14.25, + Float64: 15.25, + Foo: "foo", + Foo2: "foo2", + IntStr: 42, + UintptrStr: 44, + String: "16", + Map: map[string]Small{ + "17": {Tag: "tag17"}, + }, + MapP: map[string]*Small{ + "18": {Tag: "tag19"}, + }, + MapPNil: map[string]*Small{ + "19": nil, + }, + EmptyMap: map[string]Small{}, + Slice: []Small{{Tag: "tag20"}, {Tag: "tag21"}}, + SliceP: []*Small{{Tag: "tag22"}, nil, {Tag: "tag23"}}, + EmptySlice: []Small{}, + StringSlice: []string{"str24", "str25", "str26"}, + ByteSlice: []byte{27, 28, 29}, + Small: Small{Tag: "tag30"}, + PSmall: &Small{Tag: "tag31"}, + Interface: 5.2, } var pallValue = All{ - PBool: &allValue.Bool, - PInt: &allValue.Int, - PInt8: &allValue.Int8, - PInt16: &allValue.Int16, - PInt32: &allValue.Int32, - PInt64: &allValue.Int64, - PUint: &allValue.Uint, - PUint8: &allValue.Uint8, - PUint16: &allValue.Uint16, - PUint32: &allValue.Uint32, - PUint64: &allValue.Uint64, - PUintptr: &allValue.Uintptr, - PFloat32: &allValue.Float32, - PFloat64: &allValue.Float64, - PString: &allValue.String, - PMap: &allValue.Map, - PMapP: &allValue.MapP, - PMapPNil: &allValue.MapPNil, - PSlice: &allValue.Slice, - PSliceP: &allValue.SliceP, - PPSmall: &allValue.PSmall, - PInterface: &allValue.Interface, + PBool: &allValue.Bool, + PInt: &allValue.Int, + PInt8: &allValue.Int8, + PInt16: &allValue.Int16, + PInt32: &allValue.Int32, + PInt64: &allValue.Int64, + PUint: &allValue.Uint, + PUint8: &allValue.Uint8, + PUint16: &allValue.Uint16, + PUint32: &allValue.Uint32, + PUint64: &allValue.Uint64, + PUintptr: &allValue.Uintptr, + PFloat32: &allValue.Float32, + PFloat64: &allValue.Float64, + PString: &allValue.String, + PMap: &allValue.Map, + PMapP: &allValue.MapP, + PMapPNil: &allValue.MapPNil, + PSlice: &allValue.Slice, + PSliceP: &allValue.SliceP, + PPSmall: &allValue.PSmall, + PInterface: &allValue.Interface, } var allValueIndent = `{ - "Bool": true, - "Int": 2, - "Int8": 3, - "Int16": 4, - "Int32": 5, - "Int64": 6, - "Uint": 7, - "Uint8": 8, - "Uint16": 9, - "Uint32": 10, - "Uint64": 11, - "Uintptr": 12, - "Float32": 14.25, - "Float64": 15.25, - "bar": "foo", - "bar2": "foo2", - "IntStr": "42", - "UintptrStr": "44", - "PBool": null, - "PInt": null, - "PInt8": null, - "PInt16": null, - "PInt32": null, - "PInt64": null, - "PUint": null, - "PUint8": null, - "PUint16": null, - "PUint32": null, - "PUint64": null, - "PUintptr": null, - "PFloat32": null, - "PFloat64": null, - "String": "16", - "PString": null, - "Map": { - "17": { - "Tag": "tag17" - } - }, - "MapP": { - "18": { - "Tag": "tag19" - } - }, - "MapPNil": { - "19": null - }, - "PMap": null, - "PMapP": null, - "PMapPNil": null, - "EmptyMap": {}, - "NilMap": null, - "Slice": [ - { - "Tag": "tag20" - }, - { - "Tag": "tag21" - } - ], - "SliceP": [ - { - "Tag": "tag22" - }, - null, - { - "Tag": "tag23" - } - ], - "PSlice": null, - "PSliceP": null, - "EmptySlice": [], - "NilSlice": null, - "StringSlice": [ - "str24", - "str25", - "str26" - ], - "ByteSlice": "Gxwd", - "Small": { - "Tag": "tag30" - }, - "PSmall": { - "Tag": "tag31" - }, - "PPSmall": null, - "Interface": 5.2, - "PInterface": null + "Bool": true, + "Int": 2, + "Int8": 3, + "Int16": 4, + "Int32": 5, + "Int64": 6, + "Uint": 7, + "Uint8": 8, + "Uint16": 9, + "Uint32": 10, + "Uint64": 11, + "Uintptr": 12, + "Float32": 14.25, + "Float64": 15.25, + "bar": "foo", + "bar2": "foo2", + "IntStr": "42", + "UintptrStr": "44", + "PBool": null, + "PInt": null, + "PInt8": null, + "PInt16": null, + "PInt32": null, + "PInt64": null, + "PUint": null, + "PUint8": null, + "PUint16": null, + "PUint32": null, + "PUint64": null, + "PUintptr": null, + "PFloat32": null, + "PFloat64": null, + "String": "16", + "PString": null, + "Map": { + "17": { + "Tag": "tag17" + } + }, + "MapP": { + "18": { + "Tag": "tag19" + } + }, + "MapPNil": { + "19": null + }, + "PMap": null, + "PMapP": null, + "PMapPNil": null, + "EmptyMap": {}, + "NilMap": null, + "Slice": [ + { + "Tag": "tag20" + }, + { + "Tag": "tag21" + } + ], + "SliceP": [ + { + "Tag": "tag22" + }, + null, + { + "Tag": "tag23" + } + ], + "PSlice": null, + "PSliceP": null, + "EmptySlice": [], + "NilSlice": null, + "StringSlice": [ + "str24", + "str25", + "str26" + ], + "ByteSlice": "Gxwd", + "Small": { + "Tag": "tag30" + }, + "PSmall": { + "Tag": "tag31" + }, + "PPSmall": null, + "Interface": 5.2, + "PInterface": null }` var allValueCompact = strings.Map(noSpace, allValueIndent) var pallValueIndent = `{ - "Bool": false, - "Int": 0, - "Int8": 0, - "Int16": 0, - "Int32": 0, - "Int64": 0, - "Uint": 0, - "Uint8": 0, - "Uint16": 0, - "Uint32": 0, - "Uint64": 0, - "Uintptr": 0, - "Float32": 0, - "Float64": 0, - "bar": "", - "bar2": "", - "IntStr": "0", - "UintptrStr": "0", - "PBool": true, - "PInt": 2, - "PInt8": 3, - "PInt16": 4, - "PInt32": 5, - "PInt64": 6, - "PUint": 7, - "PUint8": 8, - "PUint16": 9, - "PUint32": 10, - "PUint64": 11, - "PUintptr": 12, - "PFloat32": 14.25, - "PFloat64": 15.25, - "String": "", - "PString": "16", - "Map": null, - "MapP": null, - "MapPNil": null, - "PMap": { - "17": { - "Tag": "tag17" - } - }, - "PMapP": { - "18": { - "Tag": "tag19" - } - }, - "PMapPNil": { - "19": null - }, - "EmptyMap": null, - "NilMap": null, - "Slice": null, - "SliceP": null, - "PSlice": [ - { - "Tag": "tag20" - }, - { - "Tag": "tag21" - } - ], - "PSliceP": [ - { - "Tag": "tag22" - }, - null, - { - "Tag": "tag23" - } - ], - "EmptySlice": null, - "NilSlice": null, - "StringSlice": null, - "ByteSlice": null, - "Small": { - "Tag": "" - }, - "PSmall": null, - "PPSmall": { - "Tag": "tag31" - }, - "Interface": null, - "PInterface": 5.2 + "Bool": false, + "Int": 0, + "Int8": 0, + "Int16": 0, + "Int32": 0, + "Int64": 0, + "Uint": 0, + "Uint8": 0, + "Uint16": 0, + "Uint32": 0, + "Uint64": 0, + "Uintptr": 0, + "Float32": 0, + "Float64": 0, + "bar": "", + "bar2": "", + "IntStr": "0", + "UintptrStr": "0", + "PBool": true, + "PInt": 2, + "PInt8": 3, + "PInt16": 4, + "PInt32": 5, + "PInt64": 6, + "PUint": 7, + "PUint8": 8, + "PUint16": 9, + "PUint32": 10, + "PUint64": 11, + "PUintptr": 12, + "PFloat32": 14.25, + "PFloat64": 15.25, + "String": "", + "PString": "16", + "Map": null, + "MapP": null, + "MapPNil": null, + "PMap": { + "17": { + "Tag": "tag17" + } + }, + "PMapP": { + "18": { + "Tag": "tag19" + } + }, + "PMapPNil": { + "19": null + }, + "EmptyMap": null, + "NilMap": null, + "Slice": null, + "SliceP": null, + "PSlice": [ + { + "Tag": "tag20" + }, + { + "Tag": "tag21" + } + ], + "PSliceP": [ + { + "Tag": "tag22" + }, + null, + { + "Tag": "tag23" + } + ], + "EmptySlice": null, + "NilSlice": null, + "StringSlice": null, + "ByteSlice": null, + "Small": { + "Tag": "" + }, + "PSmall": null, + "PPSmall": { + "Tag": "tag31" + }, + "Interface": null, + "PInterface": 5.2 }` var pallValueCompact = strings.Map(noSpace, pallValueIndent) func TestRefUnmarshal(t *testing.T) { - type S struct { - // Ref is defined in encode_test.go. - R0 Ref - R1 *Ref - R2 RefText - R3 *RefText - } - want := S{ - R0: 12, - R1: new(Ref), - R2: 13, - R3: new(RefText), - } - *want.R1 = 12 - *want.R3 = 13 + type S struct { + // Ref is defined in encode_test.go. + R0 Ref + R1 *Ref + R2 RefText + R3 *RefText + } + want := S{ + R0: 12, + R1: new(Ref), + R2: 13, + R3: new(RefText), + } + *want.R1 = 12 + *want.R3 = 13 - var got S - if err := Unmarshal([]byte(`{"R0":"ref","R1":"ref","R2":"ref","R3":"ref"}`), &got); err != nil { - t.Fatalf("Unmarshal: %v", err) - } - if !reflect.DeepEqual(got, want) { - t.Errorf("got %+v, want %+v", got, want) - } + var got S + if err := Unmarshal([]byte(`{"R0":"ref","R1":"ref","R2":"ref","R3":"ref"}`), &got); err != nil { + t.Fatalf("Unmarshal: %v", err) + } + if !reflect.DeepEqual(got, want) { + t.Errorf("got %+v, want %+v", got, want) + } } // Test that the empty string doesn't panic decoding when ,string is specified // Issue 3450 func TestEmptyString(t *testing.T) { - type T2 struct { - Number1 int `json:",string"` - Number2 int `json:",string"` - } - data := `{"Number1":"1", "Number2":""}` - var t2 T2 - err := Unmarshal([]byte(data), &t2) - if err == nil { - t.Fatal("Decode: did not return error") - } - if t2.Number1 != 1 { - t.Fatal("Decode: did not set Number1") - } + type T2 struct { + Number1 int `json:",string"` + Number2 int `json:",string"` + } + data := `{"Number1":"1", "Number2":""}` + var t2 T2 + err := Unmarshal([]byte(data), &t2) + if err == nil { + t.Fatal("Decode: did not return error") + } + if t2.Number1 != 1 { + t.Fatal("Decode: did not set Number1") + } } // Test that a null for ,string is not replaced with the previous quoted string (issue 7046). // It should also not be an error (issue 2540, issue 8587). func TestNullString(t *testing.T) { - type T struct { - A int `json:",string"` - B int `json:",string"` - C *int `json:",string"` - } - data := []byte(`{"A": "1", "B": null, "C": null}`) - var s T - s.B = 1 - s.C = new(int) - *s.C = 2 - err := Unmarshal(data, &s) - if err != nil { - t.Fatalf("Unmarshal: %v", err) - } - if s.B != 1 || s.C != nil { - t.Fatalf("after Unmarshal, s.B=%d, s.C=%p, want 1, nil", s.B, s.C) - } + type T struct { + A int `json:",string"` + B int `json:",string"` + C *int `json:",string"` + } + data := []byte(`{"A": "1", "B": null, "C": null}`) + var s T + s.B = 1 + s.C = new(int) + *s.C = 2 + err := Unmarshal(data, &s) + if err != nil { + t.Fatalf("Unmarshal: %v", err) + } + if s.B != 1 || s.C != nil { + t.Fatalf("after Unmarshal, s.B=%d, s.C=%p, want 1, nil", s.B, s.C) + } } type NullTest struct { - Bool bool - Int int - Int8 int8 - Int16 int16 - Int32 int32 - Int64 int64 - Uint uint - Uint8 uint8 - Uint16 uint16 - Uint32 uint32 - Uint64 uint64 - Float32 float32 - Float64 float64 - String string - PBool *bool - Map map[string]string - Slice []string - Interface interface{} + Bool bool + Int int + Int8 int8 + Int16 int16 + Int32 int32 + Int64 int64 + Uint uint + Uint8 uint8 + Uint16 uint16 + Uint32 uint32 + Uint64 uint64 + Float32 float32 + Float64 float64 + String string + PBool *bool + Map map[string]string + Slice []string + Interface interface{} - PRaw *json.RawMessage - PTime *time.Time - PBigInt *big.Int - PText *MustNotUnmarshalText - PBuffer *bytes.Buffer // has methods, just not relevant ones - PStruct *struct{} + PRaw *json.RawMessage + PTime *time.Time + PBigInt *big.Int + PText *MustNotUnmarshalText + PBuffer *bytes.Buffer // has methods, just not relevant ones + PStruct *struct{} - Raw json.RawMessage - Time time.Time - BigInt big.Int - Text MustNotUnmarshalText - Buffer bytes.Buffer - Struct struct{} + Raw json.RawMessage + Time time.Time + BigInt big.Int + Text MustNotUnmarshalText + Buffer bytes.Buffer + Struct struct{} } // JSON null values should be ignored for primitives and string values instead of resulting in an error. // Issue 2540 func TestUnmarshalNulls(t *testing.T) { - // Unmarshal docs: - // 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 - // on the value and produces no error. + // Unmarshal docs: + // 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 + // on the value and produces no error. - jsonData := []byte(`{ - "Bool" : null, - "Int" : null, - "Int8" : null, - "Int16" : null, - "Int32" : null, - "Int64" : null, - "Uint" : null, - "Uint8" : null, - "Uint16" : null, - "Uint32" : null, - "Uint64" : null, - "Float32" : null, - "Float64" : null, - "String" : null, - "PBool": null, - "Map": null, - "Slice": null, - "Interface": null, - "PRaw": null, - "PTime": null, - "PBigInt": null, - "PText": null, - "PBuffer": null, - "PStruct": null, - "Raw": null, - "Time": null, - "BigInt": null, - "Text": null, - "Buffer": null, - "Struct": null - }`) - nulls := NullTest{ - Bool: true, - Int: 2, - Int8: 3, - Int16: 4, - Int32: 5, - Int64: 6, - Uint: 7, - Uint8: 8, - Uint16: 9, - Uint32: 10, - Uint64: 11, - Float32: 12.1, - Float64: 13.1, - String: "14", - PBool: new(bool), - Map: map[string]string{}, - Slice: []string{}, - Interface: new(MustNotUnmarshalJSON), - PRaw: new(json.RawMessage), - PTime: new(time.Time), - PBigInt: new(big.Int), - PText: new(MustNotUnmarshalText), - PStruct: new(struct{}), - PBuffer: new(bytes.Buffer), - Raw: json.RawMessage("123"), - Time: time.Unix(123456789, 0), - BigInt: *big.NewInt(123), - } + jsonData := []byte(`{ + "Bool" : null, + "Int" : null, + "Int8" : null, + "Int16" : null, + "Int32" : null, + "Int64" : null, + "Uint" : null, + "Uint8" : null, + "Uint16" : null, + "Uint32" : null, + "Uint64" : null, + "Float32" : null, + "Float64" : null, + "String" : null, + "PBool": null, + "Map": null, + "Slice": null, + "Interface": null, + "PRaw": null, + "PTime": null, + "PBigInt": null, + "PText": null, + "PBuffer": null, + "PStruct": null, + "Raw": null, + "Time": null, + "BigInt": null, + "Text": null, + "Buffer": null, + "Struct": null + }`) + nulls := NullTest{ + Bool: true, + Int: 2, + Int8: 3, + Int16: 4, + Int32: 5, + Int64: 6, + Uint: 7, + Uint8: 8, + Uint16: 9, + Uint32: 10, + Uint64: 11, + Float32: 12.1, + Float64: 13.1, + String: "14", + PBool: new(bool), + Map: map[string]string{}, + Slice: []string{}, + Interface: new(MustNotUnmarshalJSON), + PRaw: new(json.RawMessage), + PTime: new(time.Time), + PBigInt: new(big.Int), + PText: new(MustNotUnmarshalText), + PStruct: new(struct{}), + PBuffer: new(bytes.Buffer), + Raw: json.RawMessage("123"), + Time: time.Unix(123456789, 0), + BigInt: *big.NewInt(123), + } - before := nulls.Time.String() + before := nulls.Time.String() - err := Unmarshal(jsonData, &nulls) - if err != nil { - t.Errorf("Unmarshal of null values failed: %v", err) - } - if !nulls.Bool || nulls.Int != 2 || nulls.Int8 != 3 || nulls.Int16 != 4 || nulls.Int32 != 5 || nulls.Int64 != 6 || - nulls.Uint != 7 || nulls.Uint8 != 8 || nulls.Uint16 != 9 || nulls.Uint32 != 10 || nulls.Uint64 != 11 || - nulls.Float32 != 12.1 || nulls.Float64 != 13.1 || nulls.String != "14" { - t.Errorf("Unmarshal of null values affected primitives") - } + err := Unmarshal(jsonData, &nulls) + if err != nil { + t.Errorf("Unmarshal of null values failed: %v", err) + } + if !nulls.Bool || nulls.Int != 2 || nulls.Int8 != 3 || nulls.Int16 != 4 || nulls.Int32 != 5 || nulls.Int64 != 6 || + nulls.Uint != 7 || nulls.Uint8 != 8 || nulls.Uint16 != 9 || nulls.Uint32 != 10 || nulls.Uint64 != 11 || + nulls.Float32 != 12.1 || nulls.Float64 != 13.1 || nulls.String != "14" { + t.Errorf("Unmarshal of null values affected primitives") + } - if nulls.PBool != nil { - t.Errorf("Unmarshal of null did not clear nulls.PBool") - } - if nulls.Map != nil { - t.Errorf("Unmarshal of null did not clear nulls.Map") - } - if nulls.Slice != nil { - t.Errorf("Unmarshal of null did not clear nulls.Slice") - } - if nulls.Interface != nil { - t.Errorf("Unmarshal of null did not clear nulls.Interface") - } - if nulls.PRaw != nil { - t.Errorf("Unmarshal of null did not clear nulls.PRaw") - } - if nulls.PTime != nil { - t.Errorf("Unmarshal of null did not clear nulls.PTime") - } - if nulls.PBigInt != nil { - t.Errorf("Unmarshal of null did not clear nulls.PBigInt") - } - if nulls.PText != nil { - t.Errorf("Unmarshal of null did not clear nulls.PText") - } - if nulls.PBuffer != nil { - t.Errorf("Unmarshal of null did not clear nulls.PBuffer") - } - if nulls.PStruct != nil { - t.Errorf("Unmarshal of null did not clear nulls.PStruct") - } + if nulls.PBool != nil { + t.Errorf("Unmarshal of null did not clear nulls.PBool") + } + if nulls.Map != nil { + t.Errorf("Unmarshal of null did not clear nulls.Map") + } + if nulls.Slice != nil { + t.Errorf("Unmarshal of null did not clear nulls.Slice") + } + if nulls.Interface != nil { + t.Errorf("Unmarshal of null did not clear nulls.Interface") + } + if nulls.PRaw != nil { + t.Errorf("Unmarshal of null did not clear nulls.PRaw") + } + if nulls.PTime != nil { + t.Errorf("Unmarshal of null did not clear nulls.PTime") + } + if nulls.PBigInt != nil { + t.Errorf("Unmarshal of null did not clear nulls.PBigInt") + } + if nulls.PText != nil { + t.Errorf("Unmarshal of null did not clear nulls.PText") + } + if nulls.PBuffer != nil { + t.Errorf("Unmarshal of null did not clear nulls.PBuffer") + } + if nulls.PStruct != nil { + t.Errorf("Unmarshal of null did not clear nulls.PStruct") + } - if string(nulls.Raw) != "null" { - t.Errorf("Unmarshal of json.RawMessage null did not record null: %v", string(nulls.Raw)) - } - if nulls.Time.String() != before { - t.Errorf("Unmarshal of time.Time null set time to %v", nulls.Time.String()) - } - if nulls.BigInt.String() != "123" { - t.Errorf("Unmarshal of big.Int null set int to %v", nulls.BigInt.String()) - } + if string(nulls.Raw) != "null" { + t.Errorf("Unmarshal of json.RawMessage null did not record null: %v", string(nulls.Raw)) + } + if nulls.Time.String() != before { + t.Errorf("Unmarshal of time.Time null set time to %v", nulls.Time.String()) + } + if nulls.BigInt.String() != "123" { + t.Errorf("Unmarshal of big.Int null set int to %v", nulls.BigInt.String()) + } } type MustNotUnmarshalJSON struct{} func (x MustNotUnmarshalJSON) UnmarshalJSON(_ []byte) error { - return errors.New("MustNotUnmarshalJSON was used") + return errors.New("MustNotUnmarshalJSON was used") } type MustNotUnmarshalText struct{} func (x MustNotUnmarshalText) UnmarshalText(_ []byte) error { - return errors.New("MustNotUnmarshalText was used") + return errors.New("MustNotUnmarshalText was used") } func TestStringKind(t *testing.T) { - type stringKind string + type stringKind string - var m1, m2 map[stringKind]int - m1 = map[stringKind]int{ - "foo": 42, - } + var m1, m2 map[stringKind]int + m1 = map[stringKind]int{ + "foo": 42, + } - data, err := Marshal(m1) - if err != nil { - t.Errorf("Unexpected error marshaling: %v", err) - } + data, err := Marshal(m1) + if err != nil { + t.Errorf("Unexpected error marshaling: %v", err) + } - err = Unmarshal(data, &m2) - if err != nil { - t.Errorf("Unexpected error unmarshaling: %v", err) - } + err = Unmarshal(data, &m2) + if err != nil { + t.Errorf("Unexpected error unmarshaling: %v", err) + } - if !reflect.DeepEqual(m1, m2) { - t.Error("Items should be equal after encoding and then decoding") - } + if !reflect.DeepEqual(m1, m2) { + t.Error("Items should be equal after encoding and then decoding") + } } // Custom types with []byte as underlying type could not be marshaled // and then unmarshaled. // Issue 8962. func TestByteKind(t *testing.T) { - type byteKind []byte + type byteKind []byte - a := byteKind("hello") + a := byteKind("hello") - data, err := Marshal(a) - if err != nil { - t.Error(err) - } - var b byteKind - err = Unmarshal(data, &b) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(a, b) { - t.Errorf("expected %v == %v", a, b) - } + data, err := Marshal(a) + if err != nil { + t.Error(err) + } + var b byteKind + err = Unmarshal(data, &b) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(a, b) { + t.Errorf("expected %v == %v", a, b) + } } // The fix for issue 8962 introduced a regression. // Issue 12921. func TestSliceOfCustomByte(t *testing.T) { - type Uint8 uint8 + type Uint8 uint8 - a := []Uint8("hello") + a := []Uint8("hello") - data, err := Marshal(a) - if err != nil { - t.Fatal(err) - } - var b []Uint8 - err = Unmarshal(data, &b) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(a, b) { - t.Fatalf("expected %v == %v", a, b) - } + data, err := Marshal(a) + if err != nil { + t.Fatal(err) + } + var b []Uint8 + err = Unmarshal(data, &b) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(a, b) { + t.Fatalf("expected %v == %v", a, b) + } } var decodeTypeErrorTests = []struct { - dest interface{} - src string + dest interface{} + src string }{ - {new(string), `{"user": "name"}`}, // issue 4628. - {new(error), `{}`}, // issue 4222 - {new(error), `[]`}, - {new(error), `""`}, - {new(error), `123`}, - {new(error), `true`}, + {new(string), `{"user": "name"}`}, // issue 4628. + {new(error), `{}`}, // issue 4222 + {new(error), `[]`}, + {new(error), `""`}, + {new(error), `123`}, + {new(error), `true`}, } func TestUnmarshalTypeError(t *testing.T) { - for _, item := range decodeTypeErrorTests { - err := Unmarshal([]byte(item.src), item.dest) - if _, ok := err.(*json.UnmarshalTypeError); !ok { - if _, ok = err.(decoder.SyntaxError); !ok { - t.Errorf("expected type error for Unmarshal(%q, type %T): got %T", - item.src, item.dest, err) - } - } - } + for _, item := range decodeTypeErrorTests { + err := Unmarshal([]byte(item.src), item.dest) + if _, ok := err.(*json.UnmarshalTypeError); !ok { + if _, ok = err.(decoder.SyntaxError); !ok { + t.Errorf("expected type error for Unmarshal(%q, type %T): got %T", + item.src, item.dest, err) + } + } + } } var unmarshalSyntaxTests = []string{ - "tru", - "fals", - "nul", - "123e", - `"hello`, - `[1,2,3`, - `{"key":1`, - `{"key":1,`, + "tru", + "fals", + "nul", + "123e", + `"hello`, + `[1,2,3`, + `{"key":1`, + `{"key":1,`, } func TestUnmarshalSyntax(t *testing.T) { - var x interface{} - for _, src := range unmarshalSyntaxTests { - err := Unmarshal([]byte(src), &x) - if _, ok := err.(decoder.SyntaxError); !ok { - t.Errorf("expected syntax error for Unmarshal(%q): got %T", src, err) - } - } + var x interface{} + for _, src := range unmarshalSyntaxTests { + err := Unmarshal([]byte(src), &x) + if _, ok := err.(decoder.SyntaxError); !ok { + t.Errorf("expected syntax error for Unmarshal(%q): got %T", src, err) + } + } } // Test handling of unexported fields that should be ignored. // Issue 4660 //goland:noinspection GoVetStructTag type unexportedFields struct { - Name string - m map[string]interface{} `json:"-"` - m2 map[string]interface{} `json:"abcd"` + Name string + m map[string]interface{} `json:"-"` + m2 map[string]interface{} `json:"abcd"` - s []int `json:"-"` + s []int `json:"-"` } func TestUnmarshalUnexported(t *testing.T) { - input := `{"Name": "Bob", "m": {"x": 123}, "m2": {"y": 456}, "abcd": {"z": 789}, "s": [2, 3]}` - want := &unexportedFields{Name: "Bob"} + input := `{"Name": "Bob", "m": {"x": 123}, "m2": {"y": 456}, "abcd": {"z": 789}, "s": [2, 3]}` + want := &unexportedFields{Name: "Bob"} - out := &unexportedFields{} - err := Unmarshal([]byte(input), out) - if err != nil { - t.Errorf("got error %v, expected nil", err) - } - if !reflect.DeepEqual(out, want) { - t.Errorf("got %q, want %q", out, want) - } + out := &unexportedFields{} + err := Unmarshal([]byte(input), out) + if err != nil { + t.Errorf("got error %v, expected nil", err) + } + if !reflect.DeepEqual(out, want) { + t.Errorf("got %q, want %q", out, want) + } } // Time3339 is a time.Time which encodes to and from JSON @@ -2051,199 +2053,199 @@ func TestUnmarshalUnexported(t *testing.T) { type Time3339 time.Time func (t *Time3339) UnmarshalJSON(b []byte) error { - if len(b) < 2 || b[0] != '"' || b[len(b)-1] != '"' { - return fmt.Errorf("types: failed to unmarshal non-string value %q as an RFC 3339 time", b) - } - tm, err := time.Parse(time.RFC3339, string(b[1:len(b)-1])) - if err != nil { - return err - } - *t = Time3339(tm) - return nil + if len(b) < 2 || b[0] != '"' || b[len(b)-1] != '"' { + return fmt.Errorf("types: failed to unmarshal non-string value %q as an RFC 3339 time", b) + } + tm, err := time.Parse(time.RFC3339, string(b[1:len(b)-1])) + if err != nil { + return err + } + *t = Time3339(tm) + return nil } func TestUnmarshalJSONLiteralError(t *testing.T) { - var t3 Time3339 - err := Unmarshal([]byte(`"0000-00-00T00:00:00Z"`), &t3) - if err == nil { - t.Fatalf("expected error; got time %v", time.Time(t3)) - } - if !strings.Contains(err.Error(), "range") { - t.Errorf("got err = %v; want out of range error", err) - } + var t3 Time3339 + err := Unmarshal([]byte(`"0000-00-00T00:00:00Z"`), &t3) + if err == nil { + t.Fatalf("expected error; got time %v", time.Time(t3)) + } + if !strings.Contains(err.Error(), "range") { + t.Errorf("got err = %v; want out of range error", err) + } } // Test that extra object elements in an array do not result in a // "data changing underfoot" error. // Issue 3717 func TestSkipArrayObjects(t *testing.T) { - s := `[{}]` - var dest [0]interface{} + s := `[{}]` + var dest [0]interface{} - err := Unmarshal([]byte(s), &dest) - if err != nil { - t.Errorf("got error %q, want nil", err) - } + err := Unmarshal([]byte(s), &dest) + if err != nil { + t.Errorf("got error %q, want nil", err) + } } // Test semantics of pre-filled data, such as struct fields, map elements, // slices, and arrays. // Issues 4900 and 8837, among others. func TestPrefilled(t *testing.T) { - // Values here change, cannot reuse table across runs. - var prefillTests = []struct { - in string - ptr interface{} - out interface{} - }{ - { - in: `{"X": 1, "Y": 2}`, - ptr: &XYZ{X: float32(3), Y: int16(4), Z: 1.5}, - out: &XYZ{X: float64(1), Y: float64(2), Z: 1.5}, - }, - { - in: `{"X": 1, "Y": 2}`, - ptr: &map[string]interface{}{"X": float32(3), "Y": int16(4), "Z": 1.5}, - out: &map[string]interface{}{"X": float64(1), "Y": float64(2), "Z": 1.5}, - }, - { - in: `[2]`, - ptr: &[]int{1}, - out: &[]int{2}, - }, - { - in: `[2, 3]`, - ptr: &[]int{1}, - out: &[]int{2, 3}, - }, - { - in: `[2, 3]`, - ptr: &[...]int{1}, - out: &[...]int{2}, - }, - { - in: `[3]`, - ptr: &[...]int{1, 2}, - out: &[...]int{3, 0}, - }, - } + // Values here change, cannot reuse table across runs. + var prefillTests = []struct { + in string + ptr interface{} + out interface{} + }{ + { + in: `{"X": 1, "Y": 2}`, + ptr: &XYZ{X: float32(3), Y: int16(4), Z: 1.5}, + out: &XYZ{X: float64(1), Y: float64(2), Z: 1.5}, + }, + { + in: `{"X": 1, "Y": 2}`, + ptr: &map[string]interface{}{"X": float32(3), "Y": int16(4), "Z": 1.5}, + out: &map[string]interface{}{"X": float64(1), "Y": float64(2), "Z": 1.5}, + }, + { + in: `[2]`, + ptr: &[]int{1}, + out: &[]int{2}, + }, + { + in: `[2, 3]`, + ptr: &[]int{1}, + out: &[]int{2, 3}, + }, + { + in: `[2, 3]`, + ptr: &[...]int{1}, + out: &[...]int{2}, + }, + { + in: `[3]`, + ptr: &[...]int{1, 2}, + out: &[...]int{3, 0}, + }, + } - for _, tt := range prefillTests { - ptrstr := fmt.Sprintf("%v", tt.ptr) - err := Unmarshal([]byte(tt.in), tt.ptr) // tt.ptr edited here - if err != nil { - t.Errorf("Unmarshal: %v", err) - } - if !reflect.DeepEqual(tt.ptr, tt.out) { - t.Errorf("Unmarshal(%#q, %s): have %v, want %v", tt.in, ptrstr, tt.ptr, tt.out) - } - } + for _, tt := range prefillTests { + ptrstr := fmt.Sprintf("%v", tt.ptr) + err := Unmarshal([]byte(tt.in), tt.ptr) // tt.ptr edited here + if err != nil { + t.Errorf("Unmarshal: %v", err) + } + if !reflect.DeepEqual(tt.ptr, tt.out) { + t.Errorf("Unmarshal(%#q, %s): have %v, want %v", tt.in, ptrstr, tt.ptr, tt.out) + } + } } var invalidUnmarshalTests = []struct { - v interface{} - want string + v interface{} + want string }{ - {nil, "json: Unmarshal(nil)"}, - {struct{}{}, "json: Unmarshal(non-pointer struct {})"}, - {(*int)(nil), "json: Unmarshal(nil *int)"}, + {nil, "json: Unmarshal(nil)"}, + {struct{}{}, "json: Unmarshal(non-pointer struct {})"}, + {(*int)(nil), "json: Unmarshal(nil *int)"}, } func TestInvalidUnmarshal(t *testing.T) { - buf := []byte(`{"a":"1"}`) - for _, tt := range invalidUnmarshalTests { - err := Unmarshal(buf, tt.v) - if err == nil { - t.Errorf("Unmarshal expecting error, got nil") - continue - } - if got := err.Error(); got != tt.want { - t.Errorf("Unmarshal = %q; want %q", got, tt.want) - } - } + buf := []byte(`{"a":"1"}`) + for _, tt := range invalidUnmarshalTests { + err := Unmarshal(buf, tt.v) + if err == nil { + t.Errorf("Unmarshal expecting error, got nil") + continue + } + if got := err.Error(); got != tt.want { + t.Errorf("Unmarshal = %q; want %q", got, tt.want) + } + } } var invalidUnmarshalTextTests = []struct { - v interface{} - want string + v interface{} + want string }{ - {nil, "json: Unmarshal(nil)"}, - {struct{}{}, "json: Unmarshal(non-pointer struct {})"}, - {(*int)(nil), "json: Unmarshal(nil *int)"}, - {new(net.IP), "json: cannot unmarshal number into Go value of type *net.IP"}, + {nil, "json: Unmarshal(nil)"}, + {struct{}{}, "json: Unmarshal(non-pointer struct {})"}, + {(*int)(nil), "json: Unmarshal(nil *int)"}, + {new(net.IP), "json: cannot unmarshal number into Go value of type *net.IP"}, } func TestInvalidUnmarshalText(t *testing.T) { - buf := []byte(`123`) - for _, tt := range invalidUnmarshalTextTests { - err := Unmarshal(buf, tt.v) - if err == nil { - t.Errorf("Unmarshal expecting error, got nil") - continue - } - } + buf := []byte(`123`) + for _, tt := range invalidUnmarshalTextTests { + err := Unmarshal(buf, tt.v) + if err == nil { + t.Errorf("Unmarshal expecting error, got nil") + continue + } + } } // Test that string option is ignored for invalid types. // Issue 9812. 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 interface{} `json:",string"` - P *int `json:",string"` - }{M: make(map[string]string), S: make([]string, 0), I: num, P: &num} + 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 interface{} `json:",string"` + P *int `json:",string"` + }{M: make(map[string]string), S: make([]string, 0), I: num, P: &num} - data, err := Marshal(item) - if err != nil { - t.Fatalf("Marshal: %v", err) - } + data, err := Marshal(item) + if err != nil { + t.Fatalf("Marshal: %v", err) + } - err = Unmarshal(data, &item) - if err != nil { - t.Fatalf("Unmarshal: %v", err) - } + err = Unmarshal(data, &item) + if err != nil { + t.Fatalf("Unmarshal: %v", err) + } } func TestUnmarshalErrorAfterMultipleJSON(t *testing.T) { - tests := []struct { - in string - err error - }{{ - in: `1 false null :`, - err: (&JsonSyntaxError{"invalid character ':' looking for beginning of value", 13}).err(), - }, { - in: `1 [] [,]`, - err: (&JsonSyntaxError{"invalid character ',' looking for beginning of value", 6}).err(), - }, { - in: `1 [] [true:]`, - err: (&JsonSyntaxError{"invalid character ':' after array element", 10}).err(), - }, { - in: `1 {} {"x"=}`, - err: (&JsonSyntaxError{"invalid character '=' after object key", 13}).err(), - }, { - in: `falsetruenul#`, - err: (&JsonSyntaxError{"invalid character '#' in literal null (expecting 'l')", 12}).err(), - }} - for i, tt := range tests { - dec := decoder.NewDecoder(tt.in) - var err error - for { - var v interface{} - if err = dec.Decode(&v); err != nil { - break - } - } - if v, ok := err.(decoder.SyntaxError); !ok { - t.Errorf("#%d: got %#v, want %#v", i, err, tt.err) - } else if v.Pos != int(tt.err.(*json.SyntaxError).Offset) { - t.Errorf("#%d: got %#v, want %#v", i, err, tt.err) - println(v.Description()) - } - } + tests := []struct { + in string + err error + }{{ + in: `1 false null :`, + err: (&JsonSyntaxError{"invalid character ':' looking for beginning of value", 13}).err(), + }, { + in: `1 [] [,]`, + err: (&JsonSyntaxError{"invalid character ',' looking for beginning of value", 6}).err(), + }, { + in: `1 [] [true:]`, + err: (&JsonSyntaxError{"invalid character ':' after array element", 10}).err(), + }, { + in: `1 {} {"x"=}`, + err: (&JsonSyntaxError{"invalid character '=' after object key", 13}).err(), + }, { + in: `falsetruenul#`, + err: (&JsonSyntaxError{"invalid character '#' in literal null (expecting 'l')", 12}).err(), + }} + for i, tt := range tests { + dec := decoder.NewDecoder(tt.in) + var err error + for { + var v interface{} + if err = dec.Decode(&v); err != nil { + break + } + } + if v, ok := err.(decoder.SyntaxError); !ok { + t.Errorf("#%d: got %#v, want %#v", i, err, tt.err) + } else if v.Pos != int(tt.err.(*json.SyntaxError).Offset) { + t.Errorf("#%d: got %#v, want %#v", i, err, tt.err) + println(v.Description()) + } + } } type unmarshalPanic struct{} @@ -2251,186 +2253,186 @@ type unmarshalPanic struct{} func (unmarshalPanic) UnmarshalJSON([]byte) error { panic(0xdead) } func TestUnmarshalPanic(t *testing.T) { - defer func() { - if got := recover(); !reflect.DeepEqual(got, 0xdead) { - t.Errorf("panic() = (%T)(%v), want 0xdead", got, got) - } - }() - _ = Unmarshal([]byte("{}"), &unmarshalPanic{}) - t.Fatalf("Unmarshal should have panicked") + defer func() { + if got := recover(); !reflect.DeepEqual(got, 0xdead) { + t.Errorf("panic() = (%T)(%v), want 0xdead", got, got) + } + }() + _ = Unmarshal([]byte("{}"), &unmarshalPanic{}) + t.Fatalf("Unmarshal should have panicked") } // The decoder used to hang if decoding into an interface pointing to its own address. // See golang.org/issues/31740. func TestUnmarshalRecursivePointer(t *testing.T) { - var v interface{} - v = &v - data := []byte(`{"a": "b"}`) + var v interface{} + v = &v + data := []byte(`{"a": "b"}`) - if err := Unmarshal(data, v); err != nil { - t.Fatal(err) - } + if err := Unmarshal(data, v); err != nil { + t.Fatal(err) + } } type textUnmarshalerString string func (m *textUnmarshalerString) UnmarshalText(text []byte) error { - *m = textUnmarshalerString(strings.ToLower(string(text))) - return nil + *m = textUnmarshalerString(strings.ToLower(string(text))) + return nil } // Test unmarshal to a map, where the map key is a user defined type. // See golang.org/issues/34437. func TestUnmarshalMapWithTextUnmarshalerStringKey(t *testing.T) { - var p map[textUnmarshalerString]string - if err := Unmarshal([]byte(`{"FOO": "1"}`), &p); err != nil { - t.Fatalf("Unmarshal unexpected error: %v", err) - } + var p map[textUnmarshalerString]string + if err := Unmarshal([]byte(`{"FOO": "1"}`), &p); err != nil { + t.Fatalf("Unmarshal unexpected error: %v", err) + } - if _, ok := p["foo"]; !ok { - t.Errorf(`Key "foo" does not exist in map: %v`, p) - } + if _, ok := p["foo"]; !ok { + t.Errorf(`Key "foo" does not exist in map: %v`, p) + } } func TestUnmarshalRescanLiteralMangledUnquote(t *testing.T) { - // See golang.org/issues/38105. - var p map[textUnmarshalerString]string - if err := Unmarshal([]byte(`{"开源":"12345开源"}`), &p); err != nil { - t.Fatalf("Unmarshal unexpected error: %v", err) - } - if _, ok := p["开源"]; !ok { - t.Errorf(`Key "开源" does not exist in map: %v`, p) - } + // See golang.org/issues/38105. + var p map[textUnmarshalerString]string + if err := Unmarshal([]byte(`{"开源":"12345开源"}`), &p); err != nil { + t.Fatalf("Unmarshal unexpected error: %v", err) + } + if _, ok := p["开源"]; !ok { + t.Errorf(`Key "开源" does not exist in map: %v`, p) + } - // See golang.org/issues/38126. - type T struct { - F1 string `json:"F1,string"` - } - t1 := T{"aaa\tbbb"} + // See golang.org/issues/38126. + type T struct { + F1 string `json:"F1,string"` + } + t1 := T{"aaa\tbbb"} - b, err := Marshal(t1) - if err != nil { - t.Fatalf("Marshal unexpected error: %v", err) - } - var t2 T - if err := Unmarshal(b, &t2); err != nil { - t.Fatalf("Unmarshal unexpected error: %v", err) - } - if t1 != t2 { - t.Errorf("Marshal and Unmarshal roundtrip mismatch: want %q got %q", t1, t2) - } + b, err := Marshal(t1) + if err != nil { + t.Fatalf("Marshal unexpected error: %v", err) + } + var t2 T + if err := Unmarshal(b, &t2); err != nil { + t.Fatalf("Unmarshal unexpected error: %v", err) + } + if t1 != t2 { + t.Errorf("Marshal and Unmarshal roundtrip mismatch: want %q got %q", t1, t2) + } - // See golang.org/issues/39555. - input := map[textUnmarshalerString]string{"FOO": "", `"`: ""} + // See golang.org/issues/39555. + input := map[textUnmarshalerString]string{"FOO": "", `"`: ""} - encoded, err := Marshal(input) - if err != nil { - t.Fatalf("Marshal unexpected error: %v", err) - } - var got map[textUnmarshalerString]string - if err := Unmarshal(encoded, &got); err != nil { - t.Fatalf("Unmarshal unexpected error: %v", err) - } - want := map[textUnmarshalerString]string{"foo": "", `"`: ""} - if !reflect.DeepEqual(want, got) { - t.Fatalf("Unexpected roundtrip result:\nwant: %q\ngot: %q", want, got) - } + encoded, err := Marshal(input) + if err != nil { + t.Fatalf("Marshal unexpected error: %v", err) + } + var got map[textUnmarshalerString]string + if err := Unmarshal(encoded, &got); err != nil { + t.Fatalf("Unmarshal unexpected error: %v", err) + } + want := map[textUnmarshalerString]string{"foo": "", `"`: ""} + if !reflect.DeepEqual(want, got) { + t.Fatalf("Unexpected roundtrip result:\nwant: %q\ngot: %q", want, got) + } } func TestUnmarshalMaxDepth(t *testing.T) { - testcases := []struct { - name string - data string - errMaxDepth bool - }{ - { - name: "ArrayUnderMaxNestingDepth", - data: `{"a":` + strings.Repeat(`[`, 65534) + `0` + strings.Repeat(`]`, 65534) + `}`, - errMaxDepth: false, - }, - { - name: "ArrayOverMaxNestingDepth", - data: `{"a":` + strings.Repeat(`[`, 65537) + `0` + strings.Repeat(`]`, 65537) + `}`, - errMaxDepth: true, - }, - { - name: "ArrayOverStackDepth", - data: `{"a":` + strings.Repeat(`[`, 3000000) + `0` + strings.Repeat(`]`, 3000000) + `}`, - errMaxDepth: true, - }, - { - name: "ObjectUnderMaxNestingDepth", - data: `{"a":` + strings.Repeat(`{"a":`, 65534) + `0` + strings.Repeat(`}`, 65534) + `}`, - errMaxDepth: false, - }, - { - name: "ObjectOverMaxNestingDepth", - data: `{"a":` + strings.Repeat(`{"a":`, 65537) + `0` + strings.Repeat(`}`, 65537) + `}`, - errMaxDepth: true, - }, - { - name: "ObjectOverStackDepth", - data: `{"a":` + strings.Repeat(`{"a":`, 3000000) + `0` + strings.Repeat(`}`, 3000000) + `}`, - errMaxDepth: true, - }, - } + testcases := []struct { + name string + data string + errMaxDepth bool + }{ + { + name: "ArrayUnderMaxNestingDepth", + data: `{"a":` + strings.Repeat(`[`, 65534) + `0` + strings.Repeat(`]`, 65534) + `}`, + errMaxDepth: false, + }, + { + name: "ArrayOverMaxNestingDepth", + data: `{"a":` + strings.Repeat(`[`, 65537) + `0` + strings.Repeat(`]`, 65537) + `}`, + errMaxDepth: true, + }, + { + name: "ArrayOverStackDepth", + data: `{"a":` + strings.Repeat(`[`, 3000000) + `0` + strings.Repeat(`]`, 3000000) + `}`, + errMaxDepth: true, + }, + { + name: "ObjectUnderMaxNestingDepth", + data: `{"a":` + strings.Repeat(`{"a":`, 65534) + `0` + strings.Repeat(`}`, 65534) + `}`, + errMaxDepth: false, + }, + { + name: "ObjectOverMaxNestingDepth", + data: `{"a":` + strings.Repeat(`{"a":`, 65537) + `0` + strings.Repeat(`}`, 65537) + `}`, + errMaxDepth: true, + }, + { + name: "ObjectOverStackDepth", + data: `{"a":` + strings.Repeat(`{"a":`, 3000000) + `0` + strings.Repeat(`}`, 3000000) + `}`, + errMaxDepth: true, + }, + } - targets := []struct { - name string - newValue func() interface{} - }{ - { - name: "unstructured", - newValue: func() interface{} { - var v interface{} - return &v - }, - }, - { - name: "typed named field", - newValue: func() interface{} { - v := struct { - A interface{} `json:"a"` - }{} - return &v - }, - }, - { - name: "typed missing field", - newValue: func() interface{} { - v := struct { - B interface{} `json:"b"` - }{} - return &v - }, - }, - { - name: "custom unmarshaler", - newValue: func() interface{} { - v := unmarshaler{} - return &v - }, - }, - } + targets := []struct { + name string + newValue func() interface{} + }{ + { + name: "unstructured", + newValue: func() interface{} { + var v interface{} + return &v + }, + }, + { + name: "typed named field", + newValue: func() interface{} { + v := struct { + A interface{} `json:"a"` + }{} + return &v + }, + }, + { + name: "typed missing field", + newValue: func() interface{} { + v := struct { + B interface{} `json:"b"` + }{} + return &v + }, + }, + { + name: "custom unmarshaler", + newValue: func() interface{} { + v := unmarshaler{} + return &v + }, + }, + } - for _, tc := range testcases { - for _, target := range targets { - t.Run(target.name+"-"+tc.name, func(t *testing.T) { - err := Unmarshal([]byte(tc.data), target.newValue()) - if !tc.errMaxDepth { - if err != nil { - t.Errorf("unexpected error: %v", err) - } - } else { - if err == nil { - t.Errorf("expected error containing 'exceeded max depth', got none") - } else if !strings.Contains(err.Error(), "exceeded max depth") { - t.Errorf("expected error containing 'exceeded max depth', got: %v", err) - } - } - }) - } - } + for _, tc := range testcases { + for _, target := range targets { + t.Run(target.name+"-"+tc.name, func(t *testing.T) { + err := Unmarshal([]byte(tc.data), target.newValue()) + if !tc.errMaxDepth { + if err != nil { + t.Errorf("unexpected error: %v", err) + } + } else { + if err == nil { + t.Errorf("expected error containing 'exceeded max depth', got none") + } else if !strings.Contains(err.Error(), "exceeded max depth") { + t.Errorf("expected error containing 'exceeded max depth', got: %v", err) + } + } + }) + } + } } // Issues: map value type larger than 128 bytes are stored by pointer diff --git a/decoder/decoder.go b/decoder/decoder.go index d6fadda..d59473e 100644 --- a/decoder/decoder.go +++ b/decoder/decoder.go @@ -37,6 +37,24 @@ const ( _F_allow_control = 31 ) +type Options uint64 + +const ( + OptionUseInt64 Options = 1 << _F_use_int64 + OptionUseNumber Options = 1 << _F_use_number + OptionUseUnicodeErrors Options = 1 << _F_disable_urc + OptionDisableUnknown Options = 1 << _F_disable_unknown + OptionCopyString Options = 1 << _F_copy_string +) + +func (self *Decoder) SetOptions(opts Options) { + if (opts & 1<<_F_use_number != 0) && (opts & 1<<_F_use_int64 != 0) { + panic("can't set OptionUseInt64 and OptionUseNumber both!") + } + self.f = uint64(opts) +} + + // Decoder is the decoder context object type Decoder struct { i int diff --git a/encode_test.go b/encode_test.go index 81ad835..ef88bd4 100644 --- a/encode_test.go +++ b/encode_test.go @@ -1,3 +1,5 @@ +// +build amd64 + /* * Copyright 2021 ByteDance Inc. * @@ -17,23 +19,23 @@ package sonic import ( - `os` - `bytes` - `encoding` - `encoding/json` - `fmt` - `log` - `math` - `reflect` - `regexp` - `runtime` + `os` + `bytes` + `encoding` + `encoding/json` + `fmt` + `log` + `math` + `reflect` + `regexp` + `runtime` `runtime/debug` - `strconv` - `testing` - `time` - `unsafe` + `strconv` + `testing` + `time` + `unsafe` - `github.com/bytedance/sonic/encoder` + `github.com/bytedance/sonic/encoder` ) var ( @@ -50,36 +52,36 @@ func TestMain(m *testing.M) { debug.FreeOSMemory() } println("stop GC looping!") - }() - time.Sleep(time.Millisecond) + }() + time.Sleep(time.Millisecond) m.Run() } type Optionals struct { - Sr string `json:"sr"` - So string `json:"so,omitempty"` - Sw string `json:"-"` + Sr string `json:"sr"` + So string `json:"so,omitempty"` + Sw string `json:"-"` - Ir int `json:"omitempty"` // actually named omitempty, not an option - Io int `json:"io,omitempty"` + Ir int `json:"omitempty"` // actually named omitempty, not an option + Io int `json:"io,omitempty"` - Slr []string `json:"slr,random"` - Slo []string `json:"slo,omitempty"` + 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]interface{} `json:"mr"` + Mo map[string]interface{} `json:",omitempty"` - Fr float64 `json:"fr"` - Fo float64 `json:"fo,omitempty"` + Fr float64 `json:"fr"` + Fo float64 `json:"fo,omitempty"` - Br bool `json:"br"` - Bo bool `json:"bo,omitempty"` + Br bool `json:"br"` + Bo bool `json:"bo,omitempty"` - Ur uint `json:"ur"` - Uo uint `json:"uo,omitempty"` + Ur uint `json:"ur"` + Uo uint `json:"uo,omitempty"` - Str struct{} `json:"str"` - Sto struct{} `json:"sto,omitempty"` + Str struct{} `json:"str"` + Sto struct{} `json:"sto,omitempty"` } var optionalsExpected = `{ @@ -95,89 +97,89 @@ var optionalsExpected = `{ }` func TestOmitEmpty(t *testing.T) { - var o Optionals - o.Sw = "something" - o.Mr = map[string]interface{}{} - o.Mo = map[string]interface{}{} + var o Optionals + o.Sw = "something" + o.Mr = map[string]interface{}{} + o.Mo = map[string]interface{}{} - got, err := encoder.EncodeIndented(&o, "", " ", 0) - if err != nil { - t.Fatal(err) - } - if got := string(got); got != optionalsExpected { - t.Errorf(" got: %s\nwant: %s\n", got, optionalsExpected) - } + got, err := encoder.EncodeIndented(&o, "", " ", 0) + if err != nil { + t.Fatal(err) + } + if got := string(got); got != optionalsExpected { + t.Errorf(" got: %s\nwant: %s\n", got, optionalsExpected) + } } type StringTag struct { - BoolStr bool `json:",string"` - IntStr int64 `json:",string"` - UintptrStr uintptr `json:",string"` - StrStr string `json:",string"` - NumberStr json.Number `json:",string"` + BoolStr bool `json:",string"` + IntStr int64 `json:",string"` + UintptrStr uintptr `json:",string"` + StrStr string `json:",string"` + NumberStr json.Number `json:",string"` } func TestRoundtripStringTag(t *testing.T) { - tests := []struct { - name string - in StringTag - want string // empty to just test that we roundtrip - }{ - { - name: "AllTypes", - in: StringTag{ - BoolStr: true, - IntStr: 42, - UintptrStr: 44, - StrStr: "xzbit", - NumberStr: "46", - }, - want: `{ - "BoolStr": "true", - "IntStr": "42", - "UintptrStr": "44", - "StrStr": "\"xzbit\"", - "NumberStr": "46" - }`, - }, - { - // See golang.org/issues/38173. - name: "StringDoubleEscapes", - in: StringTag{ - StrStr: "\b\f\n\r\t\"\\", - NumberStr: "0", // just to satisfy the roundtrip - }, - want: `{ - "BoolStr": "false", - "IntStr": "0", - "UintptrStr": "0", - "StrStr": "\"\\b\\f\\n\\r\\t\\\"\\\\\"", - "NumberStr": "0" - }`, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - // Indent with a tab prefix to make the multi-line string - // literals in the table nicer to read. - got, err := encoder.EncodeIndented(&test.in, "\t\t\t", "\t", 0) - if err != nil { - t.Fatal(err) - } - if got := string(got); got != test.want { - t.Fatalf(" got: %s\nwant: %s\n", got, test.want) - } + tests := []struct { + name string + in StringTag + want string // empty to just test that we roundtrip + }{ + { + name: "AllTypes", + in: StringTag{ + BoolStr: true, + IntStr: 42, + UintptrStr: 44, + StrStr: "xzbit", + NumberStr: "46", + }, + want: `{ + "BoolStr": "true", + "IntStr": "42", + "UintptrStr": "44", + "StrStr": "\"xzbit\"", + "NumberStr": "46" + }`, + }, + { + // See golang.org/issues/38173. + name: "StringDoubleEscapes", + in: StringTag{ + StrStr: "\b\f\n\r\t\"\\", + NumberStr: "0", // just to satisfy the roundtrip + }, + want: `{ + "BoolStr": "false", + "IntStr": "0", + "UintptrStr": "0", + "StrStr": "\"\\b\\f\\n\\r\\t\\\"\\\\\"", + "NumberStr": "0" + }`, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // Indent with a tab prefix to make the multi-line string + // literals in the table nicer to read. + got, err := encoder.EncodeIndented(&test.in, " ", " ", 0) + if err != nil { + t.Fatal(err) + } + if got := string(got); got != test.want { + t.Fatalf(" got: %s\nwant: %s\n", got, test.want) + } - // Verify that it round-trips. - var s2 StringTag - if err := Unmarshal(got, &s2); err != nil { - t.Fatalf("Decode: %v", err) - } - if !reflect.DeepEqual(test.in, s2) { - t.Fatalf("decode didn't match.\nsource: %#v\nEncoded as:\n%s\ndecode: %#v", test.in, string(got), s2) - } - }) - } + // Verify that it round-trips. + var s2 StringTag + if err := Unmarshal(got, &s2); err != nil { + t.Fatalf("Decode: %v", err) + } + if !reflect.DeepEqual(test.in, s2) { + t.Fatalf("decode didn't match.\nsource: %#v\nEncoded as:\n%s\ndecode: %#v", test.in, string(got), s2) + } + }) + } } // byte slices are special even if they're renamed types. @@ -186,407 +188,407 @@ type renamedByteSlice []byte type renamedRenamedByteSlice []renamedByte func TestEncodeRenamedByteSlice(t *testing.T) { - s := renamedByteSlice("abc") - result, err := Marshal(s) - if err != nil { - t.Fatal(err) - } - expect := `"YWJj"` - if string(result) != expect { - t.Errorf(" got %s want %s", result, expect) - } - r := renamedRenamedByteSlice("abc") - result, err = Marshal(r) - if err != nil { - t.Fatal(err) - } - if string(result) != expect { - t.Errorf(" got %s want %s", result, expect) - } + s := renamedByteSlice("abc") + result, err := Marshal(s) + if err != nil { + t.Fatal(err) + } + expect := `"YWJj"` + if string(result) != expect { + t.Errorf(" got %s want %s", result, expect) + } + r := renamedRenamedByteSlice("abc") + result, err = Marshal(r) + if err != nil { + t.Fatal(err) + } + if string(result) != expect { + t.Errorf(" got %s want %s", result, expect) + } } type SamePointerNoCycle struct { - Ptr1, Ptr2 *SamePointerNoCycle + Ptr1, Ptr2 *SamePointerNoCycle } var samePointerNoCycle = &SamePointerNoCycle{} type PointerCycle struct { - Ptr *PointerCycle + Ptr *PointerCycle } var pointerCycle = &PointerCycle{} type PointerCycleIndirect struct { - Ptrs []interface{} + Ptrs []interface{} } type RecursiveSlice []RecursiveSlice var ( - pointerCycleIndirect = &PointerCycleIndirect{} - mapCycle = make(map[string]interface{}) - sliceCycle = []interface{}{nil} - sliceNoCycle = []interface{}{nil, nil} - recursiveSliceCycle = []RecursiveSlice{nil} + pointerCycleIndirect = &PointerCycleIndirect{} + mapCycle = make(map[string]interface{}) + sliceCycle = []interface{}{nil} + sliceNoCycle = []interface{}{nil, nil} + recursiveSliceCycle = []RecursiveSlice{nil} ) func init() { - ptr := &SamePointerNoCycle{} - samePointerNoCycle.Ptr1 = ptr - samePointerNoCycle.Ptr2 = ptr + ptr := &SamePointerNoCycle{} + samePointerNoCycle.Ptr1 = ptr + samePointerNoCycle.Ptr2 = ptr - pointerCycle.Ptr = pointerCycle - pointerCycleIndirect.Ptrs = []interface{}{pointerCycleIndirect} + pointerCycle.Ptr = pointerCycle + pointerCycleIndirect.Ptrs = []interface{}{pointerCycleIndirect} - mapCycle["x"] = mapCycle - sliceCycle[0] = sliceCycle - sliceNoCycle[1] = sliceNoCycle[:1] - for i := 3; i > 0; i-- { - sliceNoCycle = []interface{}{sliceNoCycle} - } - recursiveSliceCycle[0] = recursiveSliceCycle + mapCycle["x"] = mapCycle + sliceCycle[0] = sliceCycle + sliceNoCycle[1] = sliceNoCycle[:1] + for i := 3; i > 0; i-- { + sliceNoCycle = []interface{}{sliceNoCycle} + } + recursiveSliceCycle[0] = recursiveSliceCycle } func TestSamePointerNoCycle(t *testing.T) { - if _, err := Marshal(samePointerNoCycle); err != nil { - t.Fatalf("unexpected error: %v", err) - } + if _, err := Marshal(samePointerNoCycle); err != nil { + t.Fatalf("unexpected error: %v", err) + } } func TestSliceNoCycle(t *testing.T) { - if _, err := Marshal(sliceNoCycle); err != nil { - t.Fatalf("unexpected error: %v", err) - } + if _, err := Marshal(sliceNoCycle); err != nil { + t.Fatalf("unexpected error: %v", err) + } } var unsupportedValues = []interface{}{ - math.NaN(), - math.Inf(-1), - math.Inf(1), - pointerCycle, - pointerCycleIndirect, - mapCycle, - sliceCycle, - recursiveSliceCycle, + math.NaN(), + math.Inf(-1), + math.Inf(1), + pointerCycle, + pointerCycleIndirect, + mapCycle, + sliceCycle, + recursiveSliceCycle, } func TestUnsupportedValues(t *testing.T) { - for _, v := range unsupportedValues { - if _, err := Marshal(v); err != nil { - if _, ok := err.(*json.UnsupportedValueError); !ok { - t.Errorf("for %v, got %T want UnsupportedValueError", v, err) - } - } else { - t.Errorf("for %v, expected error", v) - } - } + for _, v := range unsupportedValues { + if _, err := Marshal(v); err != nil { + if _, ok := err.(*json.UnsupportedValueError); !ok { + t.Errorf("for %v, got %T want UnsupportedValueError", v, err) + } + } else { + t.Errorf("for %v, expected error", v) + } + } } // Ref has Marshaler and Unmarshaler methods with pointer receiver. type Ref int func (*Ref) MarshalJSON() ([]byte, error) { - return []byte(`"ref"`), nil + return []byte(`"ref"`), nil } func (r *Ref) UnmarshalJSON([]byte) error { - *r = 12 - return nil + *r = 12 + return nil } // Val has Marshaler methods with value receiver. type Val int func (Val) MarshalJSON() ([]byte, error) { - return []byte(`"val"`), nil + return []byte(`"val"`), nil } // RefText has Marshaler and Unmarshaler methods with pointer receiver. type RefText int func (*RefText) MarshalText() ([]byte, error) { - return []byte(`"ref"`), nil + return []byte(`"ref"`), nil } func (r *RefText) UnmarshalText([]byte) error { - *r = 13 - return nil + *r = 13 + return nil } // ValText has Marshaler methods with value receiver. type ValText int func (ValText) MarshalText() ([]byte, error) { - return []byte(`"val"`), nil + return []byte(`"val"`), nil } func TestRefValMarshal(t *testing.T) { - var s = struct { - R0 Ref - R1 *Ref - R2 RefText - R3 *RefText - V0 Val - V1 *Val - V2 ValText - V3 *ValText - }{ - R0: 12, - R1: new(Ref), - R2: 14, - R3: new(RefText), - V0: 13, - V1: new(Val), - V2: 15, - V3: new(ValText), - } - const want = `{"R0":"ref","R1":"ref","R2":"\"ref\"","R3":"\"ref\"","V0":"val","V1":"val","V2":"\"val\"","V3":"\"val\""}` - b, err := Marshal(&s) - if err != nil { - t.Fatalf("Marshal: %v", err) - } - if got := string(b); got != want { - t.Errorf("got %q, want %q", got, want) - } + var s = struct { + R0 Ref + R1 *Ref + R2 RefText + R3 *RefText + V0 Val + V1 *Val + V2 ValText + V3 *ValText + }{ + R0: 12, + R1: new(Ref), + R2: 14, + R3: new(RefText), + V0: 13, + V1: new(Val), + V2: 15, + V3: new(ValText), + } + const want = `{"R0":"ref","R1":"ref","R2":"\"ref\"","R3":"\"ref\"","V0":"val","V1":"val","V2":"\"val\"","V3":"\"val\""}` + b, err := Marshal(&s) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + if got := string(b); got != want { + t.Errorf("got %q, want %q", got, want) + } } /* FIXME: disabling these test cases for now, because Sonic does not implement HTML escape - I don't think there are real usages of the `HTMLEscape` feature in real code + I don't think there are real usages of the `HTMLEscape` feature in real code // C implements Marshaler and returns unescaped JSON. type C int func (C) MarshalJSON() ([]byte, error) { - return []byte(`"<&>"`), nil + return []byte(`"<&>"`), nil } // CText implements Marshaler and returns unescaped text. type CText int func (CText) MarshalText() ([]byte, error) { - return []byte(`"<&>"`), nil + return []byte(`"<&>"`), nil } func TestMarshalerEscaping(t *testing.T) { - var c C - want := `"\u003c\u0026\u003e"` - b, err := Marshal(c) - if err != nil { - t.Fatalf("Marshal(c): %v", err) - } - if got := string(b); got != want { - t.Errorf("Marshal(c) = %#q, want %#q", got, want) - } + var c C + want := `"\u003c\u0026\u003e"` + b, err := Marshal(c) + if err != nil { + t.Fatalf("Marshal(c): %v", err) + } + if got := string(b); got != want { + t.Errorf("Marshal(c) = %#q, want %#q", got, want) + } - var ct CText - want = `"\"\u003c\u0026\u003e\""` - b, err = Marshal(ct) - if err != nil { - t.Fatalf("Marshal(ct): %v", err) - } - if got := string(b); got != want { - t.Errorf("Marshal(ct) = %#q, want %#q", got, want) - } + var ct CText + want = `"\"\u003c\u0026\u003e\""` + b, err = Marshal(ct) + if err != nil { + t.Fatalf("Marshal(ct): %v", err) + } + if got := string(b); got != want { + t.Errorf("Marshal(ct) = %#q, want %#q", got, want) + } } */ func TestAnonymousFields(t *testing.T) { - tests := []struct { - label string // Test name - makeInput func() interface{} // Function to create input value - want string // Expected JSON output - }{{ - // Both S1 and S2 have a field named X. From the perspective of S, - // it is ambiguous which one X refers to. - // This should not serialize either field. - label: "AmbiguousField", - makeInput: func() interface{} { - type ( - S1 struct{ x, X int } - S2 struct{ x, X int } - S struct { - S1 - S2 - } - ) - return S{S1{1, 2}, S2{3, 4}} - }, - want: `{}`, - }, { - label: "DominantField", - // Both S1 and S2 have a field named X, but since S has an X field as - // well, it takes precedence over S1.X and S2.X. - makeInput: func() interface{} { - type ( - S1 struct{ x, X int } - S2 struct{ x, X int } - S struct { - S1 - S2 - x, X int - } - ) - return S{S1{1, 2}, S2{3, 4}, 5, 6} - }, - want: `{"X":6}`, - }, { - // Unexported embedded field of non-struct type should not be serialized. - label: "UnexportedEmbeddedInt", - makeInput: func() interface{} { - type ( - myInt int - S struct{ myInt } - ) - return S{5} - }, - want: `{}`, - }, { - // Exported embedded field of non-struct type should be serialized. - label: "ExportedEmbeddedInt", - makeInput: func() interface{} { - type ( - MyInt int - S struct{ MyInt } - ) - return S{5} - }, - want: `{"MyInt":5}`, - }, { - // Unexported embedded field of pointer to non-struct type - // should not be serialized. - label: "UnexportedEmbeddedIntPointer", - makeInput: func() interface{} { - type ( - myInt int - S struct{ *myInt } - ) - s := S{new(myInt)} - *s.myInt = 5 - return s - }, - want: `{}`, - }, { - // Exported embedded field of pointer to non-struct type - // should be serialized. - label: "ExportedEmbeddedIntPointer", - makeInput: func() interface{} { - type ( - MyInt int - S struct{ *MyInt } - ) - s := S{new(MyInt)} - *s.MyInt = 5 - return s - }, - want: `{"MyInt":5}`, - }, { - // Exported fields of embedded structs should have their - // exported fields be serialized regardless of whether the struct types - // themselves are exported. - label: "EmbeddedStruct", - makeInput: func() interface{} { - type ( - s1 struct{ x, X int } - S2 struct{ y, Y int } - S struct { - s1 - S2 - } - ) - return S{s1{1, 2}, S2{3, 4}} - }, - want: `{"X":2,"Y":4}`, - }, { - // Exported fields of pointers to embedded structs should have their - // exported fields be serialized regardless of whether the struct types - // themselves are exported. - label: "EmbeddedStructPointer", - makeInput: func() interface{} { - type ( - s1 struct{ x, X int } - S2 struct{ y, Y int } - S struct { - *s1 - *S2 - } - ) - return S{&s1{1, 2}, &S2{3, 4}} - }, - want: `{"X":2,"Y":4}`, - }, { - // Exported fields on embedded unexported structs at multiple levels - // of nesting should still be serialized. - label: "NestedStructAndInts", - makeInput: func() interface{} { - type ( - MyInt1 int - MyInt2 int - myInt int - s2 struct { - MyInt2 - myInt - } - s1 struct { - MyInt1 - myInt - s2 - } - S struct { - s1 - myInt - } - ) - return S{s1{1, 2, s2{3, 4}}, 6} - }, - want: `{"MyInt1":1,"MyInt2":3}`, - }, { - // If an anonymous struct pointer field is nil, we should ignore - // the embedded fields behind it. Not properly doing so may - // result in the wrong output or reflect panics. - label: "EmbeddedFieldBehindNilPointer", - makeInput: func() interface{} { - type ( - S2 struct{ Field string } - S struct{ *S2 } - ) - return S{} - }, - want: `{}`, - }} + tests := []struct { + label string // Test name + makeInput func() interface{} // Function to create input value + want string // Expected JSON output + }{{ + // Both S1 and S2 have a field named X. From the perspective of S, + // it is ambiguous which one X refers to. + // This should not serialize either field. + label: "AmbiguousField", + makeInput: func() interface{} { + type ( + S1 struct{ x, X int } + S2 struct{ x, X int } + S struct { + S1 + S2 + } + ) + return S{S1{1, 2}, S2{3, 4}} + }, + want: `{}`, + }, { + label: "DominantField", + // Both S1 and S2 have a field named X, but since S has an X field as + // well, it takes precedence over S1.X and S2.X. + makeInput: func() interface{} { + type ( + S1 struct{ x, X int } + S2 struct{ x, X int } + S struct { + S1 + S2 + x, X int + } + ) + return S{S1{1, 2}, S2{3, 4}, 5, 6} + }, + want: `{"X":6}`, + }, { + // Unexported embedded field of non-struct type should not be serialized. + label: "UnexportedEmbeddedInt", + makeInput: func() interface{} { + type ( + myInt int + S struct{ myInt } + ) + return S{5} + }, + want: `{}`, + }, { + // Exported embedded field of non-struct type should be serialized. + label: "ExportedEmbeddedInt", + makeInput: func() interface{} { + type ( + MyInt int + S struct{ MyInt } + ) + return S{5} + }, + want: `{"MyInt":5}`, + }, { + // Unexported embedded field of pointer to non-struct type + // should not be serialized. + label: "UnexportedEmbeddedIntPointer", + makeInput: func() interface{} { + type ( + myInt int + S struct{ *myInt } + ) + s := S{new(myInt)} + *s.myInt = 5 + return s + }, + want: `{}`, + }, { + // Exported embedded field of pointer to non-struct type + // should be serialized. + label: "ExportedEmbeddedIntPointer", + makeInput: func() interface{} { + type ( + MyInt int + S struct{ *MyInt } + ) + s := S{new(MyInt)} + *s.MyInt = 5 + return s + }, + want: `{"MyInt":5}`, + }, { + // Exported fields of embedded structs should have their + // exported fields be serialized regardless of whether the struct types + // themselves are exported. + label: "EmbeddedStruct", + makeInput: func() interface{} { + type ( + s1 struct{ x, X int } + S2 struct{ y, Y int } + S struct { + s1 + S2 + } + ) + return S{s1{1, 2}, S2{3, 4}} + }, + want: `{"X":2,"Y":4}`, + }, { + // Exported fields of pointers to embedded structs should have their + // exported fields be serialized regardless of whether the struct types + // themselves are exported. + label: "EmbeddedStructPointer", + makeInput: func() interface{} { + type ( + s1 struct{ x, X int } + S2 struct{ y, Y int } + S struct { + *s1 + *S2 + } + ) + return S{&s1{1, 2}, &S2{3, 4}} + }, + want: `{"X":2,"Y":4}`, + }, { + // Exported fields on embedded unexported structs at multiple levels + // of nesting should still be serialized. + label: "NestedStructAndInts", + makeInput: func() interface{} { + type ( + MyInt1 int + MyInt2 int + myInt int + s2 struct { + MyInt2 + myInt + } + s1 struct { + MyInt1 + myInt + s2 + } + S struct { + s1 + myInt + } + ) + return S{s1{1, 2, s2{3, 4}}, 6} + }, + want: `{"MyInt1":1,"MyInt2":3}`, + }, { + // If an anonymous struct pointer field is nil, we should ignore + // the embedded fields behind it. Not properly doing so may + // result in the wrong output or reflect panics. + label: "EmbeddedFieldBehindNilPointer", + makeInput: func() interface{} { + type ( + S2 struct{ Field string } + S struct{ *S2 } + ) + return S{} + }, + want: `{}`, + }} - for _, tt := range tests { - t.Run(tt.label, func(t *testing.T) { - b, err := Marshal(tt.makeInput()) - if err != nil { - t.Fatalf("Marshal() = %v, want nil error", err) - } - if string(b) != tt.want { - t.Fatalf("Marshal() = %q, want %q", b, tt.want) - } - }) - } + for _, tt := range tests { + t.Run(tt.label, func(t *testing.T) { + b, err := Marshal(tt.makeInput()) + if err != nil { + t.Fatalf("Marshal() = %v, want nil error", err) + } + if string(b) != tt.want { + t.Fatalf("Marshal() = %q, want %q", b, tt.want) + } + }) + } } type BugA struct { - S string + S string } type BugB struct { - BugA - S string + BugA + S string } type BugC struct { - S string + S string } // Legal Go: We never use the repeated embedded field (S). type BugX struct { - A int - BugA - BugB + A int + BugA + BugB } // golang.org/issue/16042. @@ -595,10 +597,10 @@ type BugX struct { type nilJSONMarshaler string func (nm *nilJSONMarshaler) MarshalJSON() ([]byte, error) { - if nm == nil { - return Marshal("0zenil0") - } - return Marshal("zenil:" + string(*nm)) + if nm == nil { + return Marshal("0zenil0") + } + return Marshal("zenil:" + string(*nm)) } // golang.org/issue/34235. @@ -607,214 +609,214 @@ func (nm *nilJSONMarshaler) MarshalJSON() ([]byte, error) { type nilTextMarshaler string func (nm *nilTextMarshaler) MarshalText() ([]byte, error) { - if nm == nil { - return []byte("0zenil0"), nil - } - return []byte("zenil:" + string(*nm)), nil + if nm == nil { + return []byte("0zenil0"), nil + } + return []byte("zenil:" + string(*nm)), nil } // See golang.org/issue/16042 and golang.org/issue/34235. func TestNilMarshal(t *testing.T) { - testCases := []struct { - v interface{} - want string - }{ - {v: nil, want: `null`}, - {v: new(float64), want: `0`}, - {v: []interface{}(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 json.Marshaler }{}, want: `{"M":null}`}, - {v: struct{ M json.Marshaler }{(*nilJSONMarshaler)(nil)}, want: `{"M":"0zenil0"}`}, - {v: struct{ M interface{} }{(*nilJSONMarshaler)(nil)}, want: `{"M":null}`}, - {v: struct{ M encoding.TextMarshaler }{}, want: `{"M":null}`}, - {v: struct{ M encoding.TextMarshaler }{(*nilTextMarshaler)(nil)}, want: `{"M":"0zenil0"}`}, - {v: struct{ M interface{} }{(*nilTextMarshaler)(nil)}, want: `{"M":null}`}, - } + testCases := []struct { + v interface{} + want string + }{ + {v: nil, want: `null`}, + {v: new(float64), want: `0`}, + {v: []interface{}(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 json.Marshaler }{}, want: `{"M":null}`}, + {v: struct{ M json.Marshaler }{(*nilJSONMarshaler)(nil)}, want: `{"M":"0zenil0"}`}, + {v: struct{ M interface{} }{(*nilJSONMarshaler)(nil)}, want: `{"M":null}`}, + {v: struct{ M encoding.TextMarshaler }{}, want: `{"M":null}`}, + {v: struct{ M encoding.TextMarshaler }{(*nilTextMarshaler)(nil)}, want: `{"M":"0zenil0"}`}, + {v: struct{ M interface{} }{(*nilTextMarshaler)(nil)}, want: `{"M":null}`}, + } - for _, tt := range testCases { - out, err := Marshal(tt.v) - if err != nil || string(out) != tt.want { - t.Errorf("Marshal(%#v) = %#q, %#v, want %#q, nil", tt.v, out, err, tt.want) - continue - } - } + for _, tt := range testCases { + out, err := Marshal(tt.v) + if err != nil || string(out) != tt.want { + t.Errorf("Marshal(%#v) = %#q, %#v, want %#q, nil", tt.v, out, err, tt.want) + continue + } + } } // Issue 5245. func TestEmbeddedBug(t *testing.T) { - v := BugB{ - BugA{"A"}, - "B", - } - b, err := Marshal(v) - if err != nil { - t.Fatal("Marshal:", err) - } - want := `{"S":"B"}` - got := string(b) - if got != want { - t.Fatalf("Marshal: got %s want %s", got, want) - } - // Now check that the duplicate field, S, does not appear. - x := BugX{ - A: 23, - } - b, err = Marshal(x) - if err != nil { - t.Fatal("Marshal:", err) - } - want = `{"A":23}` - got = string(b) - if got != want { - t.Fatalf("Marshal: got %s want %s", got, want) - } + v := BugB{ + BugA{"A"}, + "B", + } + b, err := Marshal(v) + if err != nil { + t.Fatal("Marshal:", err) + } + want := `{"S":"B"}` + got := string(b) + if got != want { + t.Fatalf("Marshal: got %s want %s", got, want) + } + // Now check that the duplicate field, S, does not appear. + x := BugX{ + A: 23, + } + b, err = Marshal(x) + if err != nil { + t.Fatal("Marshal:", err) + } + want = `{"A":23}` + got = string(b) + if got != want { + t.Fatalf("Marshal: got %s want %s", got, want) + } } type BugD struct { // Same as BugA after tagging. - XXX string `json:"S"` + XXX string `json:"S"` } // BugD's tagged S field should dominate BugA's. type BugY struct { - BugA - BugD + BugA + BugD } // Test that a field with a tag dominates untagged fields. func TestTaggedFieldDominates(t *testing.T) { - v := BugY{ - BugA{"BugA"}, - BugD{"BugD"}, - } - b, err := Marshal(v) - if err != nil { - t.Fatal("Marshal:", err) - } - want := `{"S":"BugD"}` - got := string(b) - if got != want { - t.Fatalf("Marshal: got %s want %s", got, want) - } + v := BugY{ + BugA{"BugA"}, + BugD{"BugD"}, + } + b, err := Marshal(v) + if err != nil { + t.Fatal("Marshal:", err) + } + want := `{"S":"BugD"}` + got := string(b) + if got != want { + t.Fatalf("Marshal: got %s want %s", got, want) + } } // There are no tags here, so S should not appear. type BugZ struct { - BugA - BugC - BugY // Contains a tagged S field through BugD; should not dominate. + BugA + BugC + BugY // Contains a tagged S field through BugD; should not dominate. } func TestDuplicatedFieldDisappears(t *testing.T) { - v := BugZ{ - BugA{"BugA"}, - BugC{"BugC"}, - BugY{ - BugA{"nested BugA"}, - BugD{"nested BugD"}, - }, - } - b, err := Marshal(v) - if err != nil { - t.Fatal("Marshal:", err) - } - want := `{}` - got := string(b) - if got != want { - t.Fatalf("Marshal: got %s want %s", got, want) - } + v := BugZ{ + BugA{"BugA"}, + BugC{"BugC"}, + BugY{ + BugA{"nested BugA"}, + BugD{"nested BugD"}, + }, + } + b, err := Marshal(v) + if err != nil { + t.Fatal("Marshal:", err) + } + want := `{}` + got := string(b) + if got != want { + t.Fatalf("Marshal: got %s want %s", got, want) + } } func TestStdLibIssue10281(t *testing.T) { - type Foo struct { - N json.Number - } - x := Foo{json.Number(`invalid`)} + type Foo struct { + N json.Number + } + x := Foo{json.Number(`invalid`)} - b, err := Marshal(&x) - if err == nil { - t.Errorf("Marshal(&x) = %#q; want error", b) - } + b, err := Marshal(&x) + if err == nil { + t.Errorf("Marshal(&x) = %#q; want error", b) + } } // golang.org/issue/8582 func TestEncodePointerString(t *testing.T) { - type stringPointer struct { - N *int64 `json:"n,string"` - } - var n int64 = 42 - b, err := Marshal(stringPointer{N: &n}) - if err != nil { - t.Fatalf("Marshal: %v", err) - } - if got, want := string(b), `{"n":"42"}`; got != want { - t.Errorf("Marshal = %s, want %s", got, want) - } - var back stringPointer - err = Unmarshal(b, &back) - if err != nil { - t.Fatalf("Unmarshal: %v", err) - } - if back.N == nil { - t.Fatalf("Unmarshaled nil N field") - } - if *back.N != 42 { - t.Fatalf("*N = %d; want 42", *back.N) - } + type stringPointer struct { + N *int64 `json:"n,string"` + } + var n int64 = 42 + b, err := Marshal(stringPointer{N: &n}) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + if got, want := string(b), `{"n":"42"}`; got != want { + t.Errorf("Marshal = %s, want %s", got, want) + } + var back stringPointer + err = Unmarshal(b, &back) + if err != nil { + t.Fatalf("Unmarshal: %v", err) + } + if back.N == nil { + t.Fatalf("Unmarshaled nil N field") + } + if *back.N != 42 { + t.Fatalf("*N = %d; want 42", *back.N) + } } var encodeStringTests = []struct { - in string - out string + in string + out string }{ - {"\x00", `"\u0000"`}, - {"\x01", `"\u0001"`}, - {"\x02", `"\u0002"`}, - {"\x03", `"\u0003"`}, - {"\x04", `"\u0004"`}, - {"\x05", `"\u0005"`}, - {"\x06", `"\u0006"`}, - {"\x07", `"\u0007"`}, - {"\x08", `"\b"`}, - {"\x09", `"\t"`}, - {"\x0a", `"\n"`}, - {"\x0b", `"\u000b"`}, - {"\x0c", `"\f"`}, - {"\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"`}, + {"\x00", `"\u0000"`}, + {"\x01", `"\u0001"`}, + {"\x02", `"\u0002"`}, + {"\x03", `"\u0003"`}, + {"\x04", `"\u0004"`}, + {"\x05", `"\u0005"`}, + {"\x06", `"\u0006"`}, + {"\x07", `"\u0007"`}, + {"\x08", `"\b"`}, + {"\x09", `"\t"`}, + {"\x0a", `"\n"`}, + {"\x0b", `"\u000b"`}, + {"\x0c", `"\f"`}, + {"\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"`}, } func TestEncodeString(t *testing.T) { - for _, tt := range encodeStringTests { - b, err := Marshal(tt.in) - if err != nil { - t.Errorf("Marshal(%q): %v", tt.in, err) - continue - } - out := string(b) - if out != tt.out { - t.Errorf("Marshal(%q) = %#q, want %#q", tt.in, out, tt.out) - } - } + for _, tt := range encodeStringTests { + b, err := Marshal(tt.in) + if err != nil { + t.Errorf("Marshal(%q): %v", tt.in, err) + continue + } + out := string(b) + if out != tt.out { + t.Errorf("Marshal(%q) = %#q, want %#q", tt.in, out, tt.out) + } + } } type jsonbyte byte @@ -834,255 +836,255 @@ type textint int func (i textint) MarshalText() ([]byte, error) { return tenc(`TI:%d`, i) } func tenc(format string, a ...interface{}) ([]byte, error) { - var buf bytes.Buffer - _, _ = fmt.Fprintf(&buf, format, a...) - return buf.Bytes(), nil + var buf bytes.Buffer + _, _ = fmt.Fprintf(&buf, format, a...) + return buf.Bytes(), nil } // Issue 13783 func TestEncodeBytekind(t *testing.T) { - testdata := []struct { - data interface{} - want string - }{ - {byte(7), "7"}, - {jsonbyte(7), `{"JB":7}`}, - {textbyte(4), `"TB:4"`}, - {jsonint(5), `{"JI":5}`}, - {textint(1), `"TI:1"`}, - {[]byte{0, 1}, `"AAE="`}, - {[]jsonbyte{0, 1}, `[{"JB":0},{"JB":1}]`}, - {[][]jsonbyte{{0, 1}, {3}}, `[[{"JB":0},{"JB":1}],[{"JB":3}]]`}, - {[]textbyte{2, 3}, `["TB:2","TB:3"]`}, - {[]jsonint{5, 4}, `[{"JI":5},{"JI":4}]`}, - {[]textint{9, 3}, `["TI:9","TI:3"]`}, - {[]int{9, 3}, `[9,3]`}, - } - for _, d := range testdata { - js, err := Marshal(d.data) - if err != nil { - t.Error(err) - continue - } - got, want := string(js), d.want - if got != want { - t.Errorf("got %s, want %s", got, want) - } - } + testdata := []struct { + data interface{} + want string + }{ + {byte(7), "7"}, + {jsonbyte(7), `{"JB":7}`}, + {textbyte(4), `"TB:4"`}, + {jsonint(5), `{"JI":5}`}, + {textint(1), `"TI:1"`}, + {[]byte{0, 1}, `"AAE="`}, + {[]jsonbyte{0, 1}, `[{"JB":0},{"JB":1}]`}, + {[][]jsonbyte{{0, 1}, {3}}, `[[{"JB":0},{"JB":1}],[{"JB":3}]]`}, + {[]textbyte{2, 3}, `["TB:2","TB:3"]`}, + {[]jsonint{5, 4}, `[{"JI":5},{"JI":4}]`}, + {[]textint{9, 3}, `["TI:9","TI:3"]`}, + {[]int{9, 3}, `[9,3]`}, + } + for _, d := range testdata { + js, err := Marshal(d.data) + if err != nil { + t.Error(err) + continue + } + got, want := string(js), d.want + if got != want { + t.Errorf("got %s, want %s", got, want) + } + } } // https://golang.org/issue/33675 func TestNilMarshalerTextMapKey(t *testing.T) { - b, err := Marshal(map[*unmarshalerText]int{ - (*unmarshalerText)(nil): 1, - }) - if err != nil { - t.Fatalf("Failed to Marshal *text.Marshaler: %v", err) - } - const want = `{"":1}` - if string(b) != want { - t.Errorf("Marshal map with *text.Marshaler keys: got %#q, want %#q", b, want) - } + b, err := Marshal(map[*unmarshalerText]int{ + (*unmarshalerText)(nil): 1, + }) + if err != nil { + t.Fatalf("Failed to Marshal *text.Marshaler: %v", err) + } + const want = `{"":1}` + if string(b) != want { + t.Errorf("Marshal map with *text.Marshaler keys: got %#q, want %#q", b, want) + } } var re = regexp.MustCompile // syntactic checks on form of marshaled floating point numbers. var badFloatREs = []*regexp.Regexp{ - re(`p`), // no binary exponential notation - re(`^\+`), // no leading + sign - re(`^-?0[^.]`), // no unnecessary leading zeros - re(`^-?\.`), // leading zero required before decimal point - re(`\.(e|$)`), // no trailing decimal - re(`\.[0-9]+0(e|$)`), // no trailing zero in fraction - re(`^-?(0|[0-9]{2,})\..*e`), // exponential notation must have normalized mantissa - re(`e[+-]0`), // exponent must not have leading zeros - re(`e-[1-6]$`), // not tiny enough for exponential notation - re(`e+(.|1.|20)$`), // not big enough for exponential notation - re(`^-?0\.0000000`), // too tiny, should use exponential notation - re(`^-?[0-9]{22}`), // too big, should use exponential notation - re(`[1-9][0-9]{16}[1-9]`), // too many significant digits in integer - re(`[1-9][0-9.]{17}[1-9]`), // too many significant digits in decimal + re(`p`), // no binary exponential notation + re(`^\+`), // no leading + sign + re(`^-?0[^.]`), // no unnecessary leading zeros + re(`^-?\.`), // leading zero required before decimal point + re(`\.(e|$)`), // no trailing decimal + re(`\.[0-9]+0(e|$)`), // no trailing zero in fraction + re(`^-?(0|[0-9]{2,})\..*e`), // exponential notation must have normalized mantissa + re(`e[+-]0`), // exponent must not have leading zeros + re(`e-[1-6]$`), // not tiny enough for exponential notation + re(`e+(.|1.|20)$`), // not big enough for exponential notation + re(`^-?0\.0000000`), // too tiny, should use exponential notation + re(`^-?[0-9]{22}`), // too big, should use exponential notation + re(`[1-9][0-9]{16}[1-9]`), // too many significant digits in integer + re(`[1-9][0-9.]{17}[1-9]`), // too many significant digits in decimal } func TestMarshalFloat(t *testing.T) { - t.Parallel() - nfail := 0 - test := func(f float64, bits int) { - vf := interface{}(f) - if bits == 32 { - f = float64(float32(f)) // round - vf = float32(f) - } - bout, err := Marshal(vf) - if err != nil { - t.Errorf("Marshal(%T(%g)): %v", vf, vf, err) - nfail++ - return - } - out := string(bout) + t.Parallel() + nfail := 0 + test := func(f float64, bits int) { + vf := interface{}(f) + if bits == 32 { + f = float64(float32(f)) // round + vf = float32(f) + } + bout, err := Marshal(vf) + if err != nil { + t.Errorf("Marshal(%T(%g)): %v", vf, vf, err) + nfail++ + return + } + out := string(bout) - // result must convert back to the same float - g, err := strconv.ParseFloat(out, bits) - if err != nil { - t.Errorf("Marshal(%T(%g)) = %q, cannot parse back: %v", vf, vf, out, err) - nfail++ - return - } - if f != g { - t.Errorf("Marshal(%T(%g)) = %q (is %g, not %g)", vf, vf, out, float32(g), vf) - nfail++ - return - } + // result must convert back to the same float + g, err := strconv.ParseFloat(out, bits) + if err != nil { + t.Errorf("Marshal(%T(%g)) = %q, cannot parse back: %v", vf, vf, out, err) + nfail++ + return + } + if f != g { + t.Errorf("Marshal(%T(%g)) = %q (is %g, not %g)", vf, vf, out, float32(g), vf) + nfail++ + return + } - for _, re := range badFloatREs { - if re.MatchString(out) { - t.Errorf("Marshal(%T(%g)) = %q, must not match /%s/", vf, vf, out, re) - nfail++ - return - } - } - } + for _, re := range badFloatREs { + if re.MatchString(out) { + t.Errorf("Marshal(%T(%g)) = %q, must not match /%s/", vf, vf, out, re) + nfail++ + return + } + } + } - var ( - bigger = math.Inf(+1) - smaller = math.Inf(-1) - ) + var ( + bigger = math.Inf(+1) + smaller = math.Inf(-1) + ) - var digits = "1.2345678901234567890123" - for i := len(digits); i >= 2; i-- { - if testing.Short() && i < len(digits)-4 { - break - } - for exp := -30; exp <= 30; exp++ { - for _, sign := range "+-" { - for bits := 32; bits <= 64; bits += 32 { - s := fmt.Sprintf("%c%se%d", sign, digits[:i], exp) - f, err := strconv.ParseFloat(s, bits) - if err != nil { - log.Fatal(err) - } - next := math.Nextafter - if bits == 32 { - next = func(g, h float64) float64 { - return float64(math.Nextafter32(float32(g), float32(h))) - } - } - test(f, bits) - test(next(f, bigger), bits) - test(next(f, smaller), bits) - if nfail > 50 { - t.Fatalf("stopping test early") - } - } - } - } - } - test(0, 64) - test(math.Copysign(0, -1), 64) - test(0, 32) - test(math.Copysign(0, -1), 32) + var digits = "1.2345678901234567890123" + for i := len(digits); i >= 2; i-- { + if testing.Short() && i < len(digits)-4 { + break + } + for exp := -30; exp <= 30; exp++ { + for _, sign := range "+-" { + for bits := 32; bits <= 64; bits += 32 { + s := fmt.Sprintf("%c%se%d", sign, digits[:i], exp) + f, err := strconv.ParseFloat(s, bits) + if err != nil { + log.Fatal(err) + } + next := math.Nextafter + if bits == 32 { + next = func(g, h float64) float64 { + return float64(math.Nextafter32(float32(g), float32(h))) + } + } + test(f, bits) + test(next(f, bigger), bits) + test(next(f, smaller), bits) + if nfail > 50 { + t.Fatalf("stopping test early") + } + } + } + } + } + test(0, 64) + test(math.Copysign(0, -1), 64) + test(0, 32) + test(math.Copysign(0, -1), 32) } func TestMarshalRawMessageValue(t *testing.T) { - type ( - T1 struct { - M json.RawMessage `json:",omitempty"` - } - T2 struct { - M *json.RawMessage `json:",omitempty"` - } - ) + type ( + T1 struct { + M json.RawMessage `json:",omitempty"` + } + T2 struct { + M *json.RawMessage `json:",omitempty"` + } + ) - var ( - rawNil = json.RawMessage(nil) - rawEmpty = json.RawMessage([]byte{}) - rawText = json.RawMessage(`"foo"`) - ) + var ( + rawNil = json.RawMessage(nil) + rawEmpty = json.RawMessage([]byte{}) + rawText = json.RawMessage(`"foo"`) + ) - tests := []struct { - in interface{} - 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}, - {struct{ M json.RawMessage }{rawNil}, `{"M":null}`, true}, - {&struct{ M json.RawMessage }{rawNil}, `{"M":null}`, true}, - {struct{ M *json.RawMessage }{&rawNil}, `{"M":null}`, true}, - {&struct{ M *json.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}, - {T1{rawNil}, "{}", true}, - {T2{&rawNil}, `{"M":null}`, true}, - {&T1{rawNil}, "{}", true}, - {&T2{&rawNil}, `{"M":null}`, true}, + tests := []struct { + in interface{} + 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}, + {struct{ M json.RawMessage }{rawNil}, `{"M":null}`, true}, + {&struct{ M json.RawMessage }{rawNil}, `{"M":null}`, true}, + {struct{ M *json.RawMessage }{&rawNil}, `{"M":null}`, true}, + {&struct{ M *json.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}, + {T1{rawNil}, "{}", true}, + {T2{&rawNil}, `{"M":null}`, true}, + {&T1{rawNil}, "{}", true}, + {&T2{&rawNil}, `{"M":null}`, true}, - // Test with empty, but non-nil, RawMessage. - {rawEmpty, "", false}, - {&rawEmpty, "", false}, - {[]interface{}{rawEmpty}, "", false}, - {&[]interface{}{rawEmpty}, "", false}, - {[]interface{}{&rawEmpty}, "", false}, - {&[]interface{}{&rawEmpty}, "", false}, - {struct{ X json.RawMessage }{rawEmpty}, "", false}, - {&struct{ X json.RawMessage }{rawEmpty}, "", false}, - {struct{ X *json.RawMessage }{&rawEmpty}, "", false}, - {&struct{ X *json.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}, - {T1{rawEmpty}, "{}", true}, - {T2{&rawEmpty}, "", false}, - {&T1{rawEmpty}, "{}", true}, - {&T2{&rawEmpty}, "", false}, + // Test with empty, but non-nil, RawMessage. + {rawEmpty, "", false}, + {&rawEmpty, "", false}, + {[]interface{}{rawEmpty}, "", false}, + {&[]interface{}{rawEmpty}, "", false}, + {[]interface{}{&rawEmpty}, "", false}, + {&[]interface{}{&rawEmpty}, "", false}, + {struct{ X json.RawMessage }{rawEmpty}, "", false}, + {&struct{ X json.RawMessage }{rawEmpty}, "", false}, + {struct{ X *json.RawMessage }{&rawEmpty}, "", false}, + {&struct{ X *json.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}, + {T1{rawEmpty}, "{}", true}, + {T2{&rawEmpty}, "", false}, + {&T1{rawEmpty}, "{}", true}, + {&T2{&rawEmpty}, "", false}, - // Test with RawMessage with some text. - // - // The tests below marked with Issue6458 used to generate "ImZvbyI=" instead "foo". - // This behavior was intentionally changed in Go 1.8. - // See https://golang.org/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}, - {struct{ M json.RawMessage }{rawText}, `{"M":"foo"}`, true}, // Issue6458 - {&struct{ M json.RawMessage }{rawText}, `{"M":"foo"}`, true}, - {struct{ M *json.RawMessage }{&rawText}, `{"M":"foo"}`, true}, - {&struct{ M *json.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}, - {T1{rawText}, `{"M":"foo"}`, true}, // Issue6458 - {T2{&rawText}, `{"M":"foo"}`, true}, - {&T1{rawText}, `{"M":"foo"}`, true}, - {&T2{&rawText}, `{"M":"foo"}`, true}, - } + // Test with RawMessage with some text. + // + // The tests below marked with Issue6458 used to generate "ImZvbyI=" instead "foo". + // This behavior was intentionally changed in Go 1.8. + // See https://golang.org/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}, + {struct{ M json.RawMessage }{rawText}, `{"M":"foo"}`, true}, // Issue6458 + {&struct{ M json.RawMessage }{rawText}, `{"M":"foo"}`, true}, + {struct{ M *json.RawMessage }{&rawText}, `{"M":"foo"}`, true}, + {&struct{ M *json.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}, + {T1{rawText}, `{"M":"foo"}`, true}, // Issue6458 + {T2{&rawText}, `{"M":"foo"}`, true}, + {&T1{rawText}, `{"M":"foo"}`, true}, + {&T2{&rawText}, `{"M":"foo"}`, true}, + } - for i, tt := range tests { - b, err := Marshal(tt.in) - if ok := err == nil; ok != tt.ok { - if err != nil { - t.Errorf("test %d, unexpected failure: %v", i, err) - } else { - t.Errorf("test %d, unexpected success", i) - } - } - if got := string(b); got != tt.want { - t.Errorf("test %d, Marshal(%#v) = %q, want %q", i, tt.in, got, tt.want) - } - } + for i, tt := range tests { + b, err := Marshal(tt.in) + if ok := err == nil; ok != tt.ok { + if err != nil { + t.Errorf("test %d, unexpected failure: %v", i, err) + } else { + t.Errorf("test %d, unexpected success", i) + } + } + if got := string(b); got != tt.want { + t.Errorf("test %d, Marshal(%#v) = %q, want %q", i, tt.in, got, tt.want) + } + } } type marshalPanic struct{} @@ -1090,64 +1092,64 @@ type marshalPanic struct{} func (marshalPanic) MarshalJSON() ([]byte, error) { panic(0xdead) } func TestMarshalPanic(t *testing.T) { - defer func() { - if got := recover(); !reflect.DeepEqual(got, 0xdead) { - t.Errorf("panic() = (%T)(%v), want 0xdead", got, got) - } - }() - _, _ = Marshal(&marshalPanic{}) - t.Error("Marshal should have panicked") + defer func() { + if got := recover(); !reflect.DeepEqual(got, 0xdead) { + t.Errorf("panic() = (%T)(%v), want 0xdead", got, got) + } + }() + _, _ = Marshal(&marshalPanic{}) + t.Error("Marshal should have panicked") } //goland:noinspection NonAsciiCharacters func TestMarshalUncommonFieldNames(t *testing.T) { - v := struct { - A0, À, Aβ int - }{} - b, err := Marshal(v) - if err != nil { - t.Fatal("Marshal:", err) - } - want := `{"A0":0,"À":0,"Aβ":0}` - got := string(b) - if got != want { - t.Fatalf("Marshal: got %s want %s", got, want) - } + v := struct { + A0, À, Aβ int + }{} + b, err := Marshal(v) + if err != nil { + t.Fatal("Marshal:", err) + } + want := `{"A0":0,"À":0,"Aβ":0}` + got := string(b) + if got != want { + t.Fatalf("Marshal: got %s want %s", got, want) + } } type DummyMarshalerError struct { - Type reflect.Type - Err error - SourceFunc string + Type reflect.Type + Err error + SourceFunc string } func (self *DummyMarshalerError) err() *json.MarshalerError { - return (*json.MarshalerError)(unsafe.Pointer(self)) + return (*json.MarshalerError)(unsafe.Pointer(self)) } func TestMarshalerError(t *testing.T) { - s := "test variable" - st := reflect.TypeOf(s) - errText := "json: test error" + s := "test variable" + st := reflect.TypeOf(s) + errText := "json: test error" - tests := []struct { - err *json.MarshalerError - want string - }{ - { - (&DummyMarshalerError{st, fmt.Errorf(errText), ""}).err(), - "json: error calling MarshalJSON for type " + st.String() + ": " + errText, - }, - { - (&DummyMarshalerError{st, fmt.Errorf(errText), "TestMarshalerError"}).err(), - "json: error calling TestMarshalerError for type " + st.String() + ": " + errText, - }, - } + tests := []struct { + err *json.MarshalerError + want string + }{ + { + (&DummyMarshalerError{st, fmt.Errorf(errText), ""}).err(), + "json: error calling MarshalJSON for type " + st.String() + ": " + errText, + }, + { + (&DummyMarshalerError{st, fmt.Errorf(errText), "TestMarshalerError"}).err(), + "json: error calling TestMarshalerError for type " + st.String() + ": " + errText, + }, + } - for i, tt := range tests { - got := tt.err.Error() - if got != tt.want { - t.Errorf("MarshalerError test %d, got: %s, want: %s", i, got, tt.want) - } - } + for i, tt := range tests { + got := tt.err.Error() + if got != tt.want { + t.Errorf("MarshalerError test %d, got: %s, want: %s", i, got, tt.want) + } + } } diff --git a/search_test.go b/search_test.go index 6b498c7..79b4ab0 100644 --- a/search_test.go +++ b/search_test.go @@ -1,3 +1,5 @@ +// +build amd64 + /* * Copyright 2021 ByteDance Inc. * diff --git a/sonic.go b/sonic.go index 4f2d715..d087768 100644 --- a/sonic.go +++ b/sonic.go @@ -1,3 +1,5 @@ +// +build amd64 + /* * Copyright 2021 ByteDance Inc. * @@ -18,6 +20,7 @@ package sonic import ( + `io` `reflect` `github.com/bytedance/sonic/ast` @@ -28,36 +31,7 @@ import ( `github.com/bytedance/sonic/internal/rt` ) -// Marshal returns the JSON encoding bytes of v. -func Marshal(val interface{}) ([]byte, error) { - return encoder.Encode(val, 0) -} - -// MarshalString returns the JSON encoding string of v. -func MarshalString(val interface{}) (string, error) { - buf, err := encoder.Encode(val, 0) - return rt.Mem2Str(buf), err -} - -// Unmarshal parses the JSON-encoded data and stores the result in the value -// pointed to by v. -// NOTICE: This API copies given buffer by default, -// if you want to pass JSON more efficiently, use UnmarshalString instead. -func Unmarshal(buf []byte, val interface{}) error { - return UnmarshalString(string(buf), val) -} - -// UnmarshalString is like Unmarshal, except buf is a string. -func UnmarshalString(buf string, val interface{}) error { - dec := decoder.NewDecoder(buf) - err := dec.Decode(val) - pos := dec.Pos() - - /* check for errors */ - if err != nil { - return err - } - +func checkTrailings(buf string, pos int) error { /* skip all the trailing spaces */ if pos != len(buf) { for pos < len(buf) && (types.SPACE_MASK & (1 << buf[pos])) != 0 { @@ -73,11 +47,103 @@ func UnmarshalString(buf string, val interface{}) error { /* junk after JSON value */ return decoder.SyntaxError { Src : buf, - Pos : dec.Pos(), + Pos : pos, Code : types.ERR_INVALID_CHAR, } } +type frozenConfig struct { + Config + encoderOpts encoder.Options + decoderOpts decoder.Options +} + +// Froze convert the Config to API +func (cfg Config) Froze() API { + api := &frozenConfig{Config: cfg} + if cfg.EscapeHTML { + api.encoderOpts |= encoder.EscapeHTML + } + if cfg.SortMapKeys { + api.encoderOpts |= encoder.SortMapKeys + } + if cfg.CompactMarshaler { + api.encoderOpts |= encoder.CompactMarshaler + } + if cfg.NoQuoteTextMarshaler { + api.encoderOpts |= encoder.NoQuoteTextMarshaler + } + if cfg.UseInt64 { + api.decoderOpts |= decoder.OptionUseInt64 + } + if cfg.UseNumber { + api.decoderOpts |= decoder.OptionUseNumber + } + if cfg.DisallowUnknownFields { + api.decoderOpts |= decoder.OptionDisableUnknown + } + if cfg.CopyString { + api.decoderOpts |= decoder.OptionCopyString + } + return api +} + +// Marshal is implemented by sonic +func (cfg *frozenConfig) Marshal(val interface{}) ([]byte, error) { + return encoder.Encode(val, cfg.encoderOpts) +} + +// MarshalToString is implemented by sonic +func (cfg *frozenConfig) MarshalToString(val interface{}) (string, error) { + buf, err := encoder.Encode(val, cfg.encoderOpts) + return rt.Mem2Str(buf), err +} + +// MarshalIndent is implemented by sonic +func (cfg *frozenConfig) MarshalIndent(val interface{}, prefix, indent string) ([]byte, error) { + return encoder.EncodeIndented(val, prefix, indent, cfg.encoderOpts) +} + +// UnmarshalFromString is implemented by sonic +func (cfg *frozenConfig) UnmarshalFromString(buf string, val interface{}) error { + dec := decoder.NewDecoder(buf) + dec.SetOptions(cfg.decoderOpts) + + err := dec.Decode(val) + pos := dec.Pos() + + /* check for errors */ + if err != nil { + return err + } + return checkTrailings(buf, pos) +} + +// Unmarshal is implemented by sonic +func (cfg *frozenConfig) Unmarshal(buf []byte, val interface{}) error { + return cfg.UnmarshalFromString(string(buf), val) +} + +// NewEncoder is implemented by sonic +func (cfg *frozenConfig) NewEncoder(writer io.Writer) Encoder { + enc := encoder.NewStreamEncoder(writer) + enc.Opts = cfg.encoderOpts + return enc +} + +// NewDecoder is implemented by sonic +func (cfg *frozenConfig) NewDecoder(reader io.Reader) Decoder { + dec := decoder.NewStreamDecoder(reader) + dec.SetOptions(cfg.decoderOpts) + return dec +} + +// Valid is implemented by sonic +func (cfg *frozenConfig) Valid(data []byte) bool { + ok, _ := encoder.Valid(data) + return ok +} + // Pretouch compiles vt ahead-of-time to avoid JIT compilation on-the-fly, in // order to reduce the first-hit latency. // diff --git a/use_number_test.go b/use_number_test.go index 3552789..a0197c4 100644 --- a/use_number_test.go +++ b/use_number_test.go @@ -1,3 +1,5 @@ +// +build amd64 + /* * Copyright 2021 ByteDance Inc. * @@ -17,98 +19,98 @@ package sonic import ( - "encoding/json" - "reflect" - "testing" + `encoding/json` + `reflect` + `testing` - "github.com/bytedance/sonic/decoder" + `github.com/bytedance/sonic/decoder` ) type useInt64Test struct { - in string - out int64 + in string + out int64 } type useFloatTest struct { - in string - out float64 + in string + out float64 } var useinttest = []useInt64Test{ - // int64 - {"0", 0}, - {"1", 1}, - {"-1", -1}, - {"100", 100}, + // int64 + {"0", 0}, + {"1", 1}, + {"-1", -1}, + {"100", 100}, - {"-9223372036854775807", -9223372036854775807}, - {"-9223372036854775808", -9223372036854775808}, //min int64 - {"9223372036854775807", 9223372036854775807}, //max int64 - {"9223372036854775806", 9223372036854775806}, + {"-9223372036854775807", -9223372036854775807}, + {"-9223372036854775808", -9223372036854775808}, //min int64 + {"9223372036854775807", 9223372036854775807}, //max int64 + {"9223372036854775806", 9223372036854775806}, } var usefloattest = []useFloatTest{ - // float64 - {"-9223372036854775809", -9223372036854775809}, // int64 overflow - {"9223372036854775808", 9223372036854775808}, // int64 overflow - {"1e2", 1e2}, - {"1e-20", 1e-20}, - {"1.0", 1}, + // float64 + {"-9223372036854775809", -9223372036854775809}, // int64 overflow + {"9223372036854775808", 9223372036854775808}, // int64 overflow + {"1e2", 1e2}, + {"1e-20", 1e-20}, + {"1.0", 1}, } func TestUseInt64(t *testing.T) { - for i, tt := range useinttest { - var sout interface{} - dc := decoder.NewDecoder(tt.in) - dc.UseInt64() - serr := dc.Decode(&sout) - if !reflect.DeepEqual(sout, tt.out) { - t.Errorf("Test %d, %#v\ngot:\n %#v\nexp:\n %#v\n", i, tt.in, sout, tt.in) - } - if serr != nil { - t.Errorf("Test %d, %#v\ngot:\n %#v\nexp:\n nil\n", i, tt, serr) - } - } + for i, tt := range useinttest { + var sout interface{} + dc := decoder.NewDecoder(tt.in) + dc.UseInt64() + serr := dc.Decode(&sout) + if !reflect.DeepEqual(sout, tt.out) { + t.Errorf("Test %d, %#v\ngot:\n %#v\nexp:\n %#v\n", i, tt.in, sout, tt.in) + } + if serr != nil { + t.Errorf("Test %d, %#v\ngot:\n %#v\nexp:\n nil\n", i, tt, serr) + } + } - for i, tt := range usefloattest { - var sout interface{} - dc := decoder.NewDecoder(tt.in) - dc.UseInt64() - //the input string is not int64, still return float64 - serr := dc.Decode(&sout) - if !reflect.DeepEqual(sout, tt.out) { - t.Errorf("Test %d, %#v\ngot:\n %#v\nexp:\n %#v\n", i, tt.in, sout, tt.in) - } - if serr != nil { - t.Errorf("Test %d, %#v\ngot:\n %#v\nexp:\n nil\n", i, tt, serr) - } - } + for i, tt := range usefloattest { + var sout interface{} + dc := decoder.NewDecoder(tt.in) + dc.UseInt64() + //the input string is not int64, still return float64 + serr := dc.Decode(&sout) + if !reflect.DeepEqual(sout, tt.out) { + t.Errorf("Test %d, %#v\ngot:\n %#v\nexp:\n %#v\n", i, tt.in, sout, tt.in) + } + if serr != nil { + t.Errorf("Test %d, %#v\ngot:\n %#v\nexp:\n nil\n", i, tt, serr) + } + } } func TestUseNumber(t *testing.T) { - for i, tt := range useinttest { - var sout interface{} - dc := decoder.NewDecoder(tt.in) - dc.UseNumber() - serr := dc.Decode(&sout) - if !reflect.DeepEqual(sout, json.Number(tt.in)) { - t.Errorf("Test %d, %#v\ngot:\n %#v\nexp:\n %#v\n", i, tt.in, sout, tt.out) - } - if serr != nil { - t.Errorf("Test %d, %#v\ngot:\n %#v\nexp:\n nil\n", i, tt, serr) - } - } + for i, tt := range useinttest { + var sout interface{} + dc := decoder.NewDecoder(tt.in) + dc.UseNumber() + serr := dc.Decode(&sout) + if !reflect.DeepEqual(sout, json.Number(tt.in)) { + t.Errorf("Test %d, %#v\ngot:\n %#v\nexp:\n %#v\n", i, tt.in, sout, tt.out) + } + if serr != nil { + t.Errorf("Test %d, %#v\ngot:\n %#v\nexp:\n nil\n", i, tt, serr) + } + } - for i, tt := range usefloattest { - var sout interface{} - dc := decoder.NewDecoder(tt.in) - dc.UseNumber() - serr := dc.Decode(&sout) - if !reflect.DeepEqual(sout, json.Number(tt.in)) { - t.Errorf("Test %d, %#v\ngot:\n %#v\nexp:\n %#v\n", i, tt.in, sout, tt.out) - } - if serr != nil { - t.Errorf("Test %d, %#v\ngot:\n %#v\nexp:\n nil\n", i, tt, serr) - } - } + for i, tt := range usefloattest { + var sout interface{} + dc := decoder.NewDecoder(tt.in) + dc.UseNumber() + serr := dc.Decode(&sout) + if !reflect.DeepEqual(sout, json.Number(tt.in)) { + t.Errorf("Test %d, %#v\ngot:\n %#v\nexp:\n %#v\n", i, tt.in, sout, tt.out) + } + if serr != nil { + t.Errorf("Test %d, %#v\ngot:\n %#v\nexp:\n nil\n", i, tt, serr) + } + } }