| // 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" |
| "math" |
| "reflect" |
| "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) { |
| // 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 TestIssue6458(t *testing.T) { |
| type Foo struct { |
| M RawMessage |
| } |
| x := Foo{RawMessage(`"foo"`)} |
| |
| b, err := Marshal(&x) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if want := `{"M":"foo"}`; string(b) != want { |
| t.Errorf("Marshal(&x) = %#q; want %#q", b, want) |
| } |
| |
| b, err = Marshal(x) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| if want := `{"M":"ImZvbyI="}`; string(b) != want { |
| t.Errorf("Marshal(x) = %#q; want %#q", b, want) |
| } |
| } |
| |
| 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) |
| } |
| } |