| // Copyright 2011 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package json | |
| import ( | |
| "bytes" | |
| "fmt" | |
| "log" | |
| "math" | |
| "reflect" | |
| "regexp" | |
| "strconv" | |
| "testing" | |
| "unicode" | |
| ) | |
| type Optionals struct { | |
| 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"` | |
| Slr []string `json:"slr,random"` | |
| Slo []string `json:"slo,omitempty"` | |
| Mr map[string]interface{} `json:"mr"` | |
| Mo map[string]interface{} `json:",omitempty"` | |
| Fr float64 `json:"fr"` | |
| Fo float64 `json:"fo,omitempty"` | |
| Br bool `json:"br"` | |
| Bo bool `json:"bo,omitempty"` | |
| Ur uint `json:"ur"` | |
| Uo uint `json:"uo,omitempty"` | |
| Str struct{} `json:"str"` | |
| Sto struct{} `json:"sto,omitempty"` | |
| } | |
| var optionalsExpected = `{ | |
| "sr": "", | |
| "omitempty": 0, | |
| "slr": null, | |
| "mr": {}, | |
| "fr": 0, | |
| "br": false, | |
| "ur": 0, | |
| "str": {}, | |
| "sto": {} | |
| }` | |
| func TestOmitEmpty(t *testing.T) { | |
| var o Optionals | |
| o.Sw = "something" | |
| o.Mr = map[string]interface{}{} | |
| o.Mo = map[string]interface{}{} | |
| got, err := MarshalIndent(&o, "", " ") | |
| 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"` | |
| StrStr string `json:",string"` | |
| } | |
| var stringTagExpected = `{ | |
| "BoolStr": "true", | |
| "IntStr": "42", | |
| "StrStr": "\"xzbit\"" | |
| }` | |
| func TestStringTag(t *testing.T) { | |
| var s StringTag | |
| s.BoolStr = true | |
| s.IntStr = 42 | |
| s.StrStr = "xzbit" | |
| got, err := MarshalIndent(&s, "", " ") | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if got := string(got); got != stringTagExpected { | |
| t.Fatalf(" got: %s\nwant: %s\n", got, stringTagExpected) | |
| } | |
| // Verify that it round-trips. | |
| var s2 StringTag | |
| err = NewDecoder(bytes.NewReader(got)).Decode(&s2) | |
| if err != nil { | |
| t.Fatalf("Decode: %v", err) | |
| } | |
| if !reflect.DeepEqual(s, s2) { | |
| t.Fatalf("decode didn't match.\nsource: %#v\nEncoded as:\n%s\ndecode: %#v", s, string(got), s2) | |
| } | |
| } | |
| // byte slices are special even if they're renamed types. | |
| type renamedByte byte | |
| 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) | |
| } | |
| } | |
| var unsupportedValues = []interface{}{ | |
| math.NaN(), | |
| math.Inf(-1), | |
| math.Inf(1), | |
| } | |
| func TestUnsupportedValues(t *testing.T) { | |
| for _, v := range unsupportedValues { | |
| if _, err := Marshal(v); err != nil { | |
| if _, ok := err.(*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 | |
| } | |
| func (r *Ref) UnmarshalJSON([]byte) error { | |
| *r = 12 | |
| return nil | |
| } | |
| // Val has Marshaler methods with value receiver. | |
| type Val int | |
| func (Val) MarshalJSON() ([]byte, error) { | |
| 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 | |
| } | |
| func (r *RefText) UnmarshalText([]byte) error { | |
| *r = 13 | |
| return nil | |
| } | |
| // ValText has Marshaler methods with value receiver. | |
| type ValText int | |
| func (ValText) MarshalText() ([]byte, error) { | |
| 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) | |
| } | |
| } | |
| // C implements Marshaler and returns unescaped JSON. | |
| type C int | |
| func (C) MarshalJSON() ([]byte, error) { | |
| return []byte(`"<&>"`), nil | |
| } | |
| // CText implements Marshaler and returns unescaped text. | |
| type CText int | |
| func (CText) MarshalText() ([]byte, error) { | |
| 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 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) | |
| } | |
| } | |
| type IntType int | |
| type MyStruct struct { | |
| IntType | |
| } | |
| func TestAnonymousNonstruct(t *testing.T) { | |
| var i IntType = 11 | |
| a := MyStruct{i} | |
| const want = `{"IntType":11}` | |
| b, err := Marshal(a) | |
| if err != nil { | |
| t.Fatalf("Marshal: %v", err) | |
| } | |
| if got := string(b); got != want { | |
| t.Errorf("got %q, want %q", got, want) | |
| } | |
| } | |
| type BugA struct { | |
| S string | |
| } | |
| type BugB struct { | |
| BugA | |
| S string | |
| } | |
| type BugC struct { | |
| S string | |
| } | |
| // Legal Go: We never use the repeated embedded field (S). | |
| type BugX struct { | |
| A int | |
| BugA | |
| BugB | |
| } | |
| // 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) | |
| } | |
| } | |
| type BugD struct { // Same as BugA after tagging. | |
| XXX string `json:"S"` | |
| } | |
| // BugD's tagged S field should dominate BugA's. | |
| type BugY struct { | |
| 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) | |
| } | |
| } | |
| // 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. | |
| } | |
| 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) | |
| } | |
| } | |
| func TestStringBytes(t *testing.T) { | |
| t.Parallel() | |
| // Test that encodeState.stringBytes and encodeState.string use the same encoding. | |
| var r []rune | |
| for i := '\u0000'; i <= unicode.MaxRune; i++ { | |
| r = append(r, i) | |
| } | |
| s := string(r) + "\xff\xff\xffhello" // some invalid UTF-8 too | |
| for _, escapeHTML := range []bool{true, false} { | |
| es := &encodeState{} | |
| es.string(s, escapeHTML) | |
| esBytes := &encodeState{} | |
| esBytes.stringBytes([]byte(s), escapeHTML) | |
| enc := es.Buffer.String() | |
| encBytes := esBytes.Buffer.String() | |
| if enc != encBytes { | |
| i := 0 | |
| for i < len(enc) && i < len(encBytes) && enc[i] == encBytes[i] { | |
| i++ | |
| } | |
| enc = enc[i:] | |
| encBytes = encBytes[i:] | |
| i = 0 | |
| for i < len(enc) && i < len(encBytes) && enc[len(enc)-i-1] == encBytes[len(encBytes)-i-1] { | |
| i++ | |
| } | |
| enc = enc[:len(enc)-i] | |
| encBytes = encBytes[:len(encBytes)-i] | |
| if len(enc) > 20 { | |
| enc = enc[:20] + "..." | |
| } | |
| if len(encBytes) > 20 { | |
| encBytes = encBytes[:20] + "..." | |
| } | |
| t.Errorf("with escapeHTML=%t, encodings differ at %#q vs %#q", | |
| escapeHTML, enc, encBytes) | |
| } | |
| } | |
| } | |
| func TestIssue10281(t *testing.T) { | |
| type Foo struct { | |
| N Number | |
| } | |
| x := Foo{Number(`invalid`)} | |
| b, err := Marshal(&x) | |
| if err == nil { | |
| t.Errorf("Marshal(&x) = %#q; want error", b) | |
| } | |
| } | |
| func TestHTMLEscape(t *testing.T) { | |
| var b, want bytes.Buffer | |
| m := `{"M":"<html>foo &` + "\xe2\x80\xa8 \xe2\x80\xa9" + `</html>"}` | |
| want.Write([]byte(`{"M":"\u003chtml\u003efoo \u0026\u2028 \u2029\u003c/html\u003e"}`)) | |
| HTMLEscape(&b, []byte(m)) | |
| if !bytes.Equal(b.Bytes(), want.Bytes()) { | |
| t.Errorf("HTMLEscape(&b, []byte(m)) = %s; want %s", b.Bytes(), want.Bytes()) | |
| } | |
| } | |
| // 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("Unmarshalled nil N field") | |
| } | |
| if *back.N != 42 { | |
| t.Fatalf("*N = %d; want 42", *back.N) | |
| } | |
| } | |
| var encodeStringTests = []struct { | |
| in string | |
| out string | |
| }{ | |
| {"\x00", `"\u0000"`}, | |
| {"\x01", `"\u0001"`}, | |
| {"\x02", `"\u0002"`}, | |
| {"\x03", `"\u0003"`}, | |
| {"\x04", `"\u0004"`}, | |
| {"\x05", `"\u0005"`}, | |
| {"\x06", `"\u0006"`}, | |
| {"\x07", `"\u0007"`}, | |
| {"\x08", `"\u0008"`}, | |
| {"\x09", `"\t"`}, | |
| {"\x0a", `"\n"`}, | |
| {"\x0b", `"\u000b"`}, | |
| {"\x0c", `"\u000c"`}, | |
| {"\x0d", `"\r"`}, | |
| {"\x0e", `"\u000e"`}, | |
| {"\x0f", `"\u000f"`}, | |
| {"\x10", `"\u0010"`}, | |
| {"\x11", `"\u0011"`}, | |
| {"\x12", `"\u0012"`}, | |
| {"\x13", `"\u0013"`}, | |
| {"\x14", `"\u0014"`}, | |
| {"\x15", `"\u0015"`}, | |
| {"\x16", `"\u0016"`}, | |
| {"\x17", `"\u0017"`}, | |
| {"\x18", `"\u0018"`}, | |
| {"\x19", `"\u0019"`}, | |
| {"\x1a", `"\u001a"`}, | |
| {"\x1b", `"\u001b"`}, | |
| {"\x1c", `"\u001c"`}, | |
| {"\x1d", `"\u001d"`}, | |
| {"\x1e", `"\u001e"`}, | |
| {"\x1f", `"\u001f"`}, | |
| } | |
| 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) | |
| } | |
| } | |
| } | |
| type jsonbyte byte | |
| func (b jsonbyte) MarshalJSON() ([]byte, error) { return tenc(`{"JB":%d}`, b) } | |
| type textbyte byte | |
| func (b textbyte) MarshalText() ([]byte, error) { return tenc(`TB:%d`, b) } | |
| type jsonint int | |
| func (i jsonint) MarshalJSON() ([]byte, error) { return tenc(`{"JI":%d}`, i) } | |
| 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 | |
| } | |
| // 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) | |
| } | |
| } | |
| } | |
| func TestTextMarshalerMapKeysAreSorted(t *testing.T) { | |
| b, err := Marshal(map[unmarshalerText]int{ | |
| {"x", "y"}: 1, | |
| {"y", "x"}: 2, | |
| {"a", "z"}: 3, | |
| {"z", "a"}: 4, | |
| }) | |
| if err != nil { | |
| t.Fatalf("Failed to Marshal text.Marshaler: %v", err) | |
| } | |
| const want = `{"a:z":3,"x:y":1,"y:x":2,"z:a":4}` | |
| 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 marshalled 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-9]`), // positive exponent must be signed | |
| 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 | |
| // below here for float32 only | |
| re(`[1-9][0-9]{8}[1-9]`), // too many significant digits in integer | |
| re(`[1-9][0-9.]{9}[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) | |
| // 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 || fmt.Sprint(f) != fmt.Sprint(g) { // fmt.Sprint handles ±0 | |
| t.Errorf("Marshal(%T(%g)) = %q (is %g, not %g)", vf, vf, out, float32(g), vf) | |
| nfail++ | |
| return | |
| } | |
| bad := badFloatREs | |
| if bits == 64 { | |
| bad = bad[:len(bad)-2] | |
| } | |
| for _, re := range bad { | |
| 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 digits = "1.2345678901234567890123" | |
| for i := len(digits); i >= 2; i-- { | |
| 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 RawMessage `json:",omitempty"` | |
| } | |
| T2 struct { | |
| M *RawMessage `json:",omitempty"` | |
| } | |
| ) | |
| var ( | |
| rawNil = RawMessage(nil) | |
| rawEmpty = RawMessage([]byte{}) | |
| rawText = RawMessage([]byte(`"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 RawMessage }{rawNil}, `{"M":null}`, true}, | |
| {&struct{ M RawMessage }{rawNil}, `{"M":null}`, true}, | |
| {struct{ M *RawMessage }{&rawNil}, `{"M":null}`, true}, | |
| {&struct{ M *RawMessage }{&rawNil}, `{"M":null}`, true}, | |
| {map[string]interface{}{"M": rawNil}, `{"M":null}`, true}, | |
| {&map[string]interface{}{"M": rawNil}, `{"M":null}`, true}, | |
| {map[string]interface{}{"M": &rawNil}, `{"M":null}`, true}, | |
| {&map[string]interface{}{"M": &rawNil}, `{"M":null}`, true}, | |
| {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 RawMessage }{rawEmpty}, "", false}, | |
| {&struct{ X RawMessage }{rawEmpty}, "", false}, | |
| {struct{ X *RawMessage }{&rawEmpty}, "", false}, | |
| {&struct{ X *RawMessage }{&rawEmpty}, "", false}, | |
| {map[string]interface{}{"nil": rawEmpty}, "", false}, | |
| {&map[string]interface{}{"nil": rawEmpty}, "", false}, | |
| {map[string]interface{}{"nil": &rawEmpty}, "", false}, | |
| {&map[string]interface{}{"nil": &rawEmpty}, "", false}, | |
| {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://github.com/golang/go/issues/14493#issuecomment-255857318 | |
| {rawText, `"foo"`, true}, // Issue6458 | |
| {&rawText, `"foo"`, true}, | |
| {[]interface{}{rawText}, `["foo"]`, true}, // Issue6458 | |
| {&[]interface{}{rawText}, `["foo"]`, true}, // Issue6458 | |
| {[]interface{}{&rawText}, `["foo"]`, true}, | |
| {&[]interface{}{&rawText}, `["foo"]`, true}, | |
| {struct{ M RawMessage }{rawText}, `{"M":"foo"}`, true}, // Issue6458 | |
| {&struct{ M RawMessage }{rawText}, `{"M":"foo"}`, true}, | |
| {struct{ M *RawMessage }{&rawText}, `{"M":"foo"}`, true}, | |
| {&struct{ M *RawMessage }{&rawText}, `{"M":"foo"}`, true}, | |
| {map[string]interface{}{"M": rawText}, `{"M":"foo"}`, true}, // Issue6458 | |
| {&map[string]interface{}{"M": rawText}, `{"M":"foo"}`, true}, // Issue6458 | |
| {map[string]interface{}{"M": &rawText}, `{"M":"foo"}`, true}, | |
| {&map[string]interface{}{"M": &rawText}, `{"M":"foo"}`, true}, | |
| {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) | |
| } | |
| } | |
| } |