| // Copyright 2018 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 ( |
| "math" |
| "strings" |
| "testing" |
| "unicode/utf8" |
| |
| "github.com/google/go-cmp/cmp" |
| "github.com/google/go-cmp/cmp/cmpopts" |
| ) |
| |
| func Test(t *testing.T) { |
| const space = " \n\r\t" |
| var V = ValueOf |
| type Arr = []Value |
| type Obj = [][2]Value |
| |
| tests := []struct { |
| in string |
| wantVal Value |
| wantOut string |
| wantOutIndent string |
| wantErr string |
| }{{ |
| in: ``, |
| wantErr: `unexpected EOF`, |
| }, { |
| in: space, |
| wantErr: `unexpected EOF`, |
| }, { |
| in: space + `null` + space, |
| wantVal: V(nil), |
| wantOut: `null`, |
| wantOutIndent: `null`, |
| }, { |
| in: space + `true` + space, |
| wantVal: V(true), |
| wantOut: `true`, |
| wantOutIndent: `true`, |
| }, { |
| in: space + `false` + space, |
| wantVal: V(false), |
| wantOut: `false`, |
| wantOutIndent: `false`, |
| }, { |
| in: space + `0` + space, |
| wantVal: V(0.0), |
| wantOut: `0`, |
| wantOutIndent: `0`, |
| }, { |
| in: space + `"hello"` + space, |
| wantVal: V("hello"), |
| wantOut: `"hello"`, |
| wantOutIndent: `"hello"`, |
| }, { |
| in: space + `[]` + space, |
| wantVal: V(Arr{}), |
| wantOut: `[]`, |
| wantOutIndent: `[]`, |
| }, { |
| in: space + `{}` + space, |
| wantVal: V(Obj{}), |
| wantOut: `{}`, |
| wantOutIndent: `{}`, |
| }, { |
| in: `null#invalid`, |
| wantErr: `8 bytes of unconsumed input`, |
| }, { |
| in: `0#invalid`, |
| wantErr: `8 bytes of unconsumed input`, |
| }, { |
| in: `"hello"#invalid`, |
| wantErr: `8 bytes of unconsumed input`, |
| }, { |
| in: `[]#invalid`, |
| wantErr: `8 bytes of unconsumed input`, |
| }, { |
| in: `{}#invalid`, |
| wantErr: `8 bytes of unconsumed input`, |
| }, { |
| in: `[truee,true]`, |
| wantErr: `invalid "truee" as literal`, |
| }, { |
| in: `[falsee,false]`, |
| wantErr: `invalid "falsee" as literal`, |
| }, { |
| in: `[`, |
| wantErr: `unexpected EOF`, |
| }, { |
| in: `[{}]`, |
| wantVal: V(Arr{V(Obj{})}), |
| wantOut: "[{}]", |
| wantOutIndent: "[\n\t{}\n]", |
| }, { |
| in: `[{]}`, |
| wantErr: `invalid character ']' at start of string`, |
| }, { |
| in: `[,]`, |
| wantErr: `invalid "," as value`, |
| }, { |
| in: `{,}`, |
| wantErr: `invalid character ',' at start of string`, |
| }, { |
| in: `{"key""val"}`, |
| wantErr: `invalid character '"', expected ':' in object`, |
| }, { |
| in: `["elem0""elem1"]`, |
| wantErr: `invalid character '"', expected ']' at end of array`, |
| }, { |
| in: `{"hello"`, |
| wantErr: `unexpected EOF`, |
| }, { |
| in: `{"hello"}`, |
| wantErr: `invalid character '}', expected ':' in object`, |
| }, { |
| in: `{"hello":`, |
| wantErr: `unexpected EOF`, |
| }, { |
| in: `{"hello":}`, |
| wantErr: `invalid "}" as value`, |
| }, { |
| in: `{"hello":"goodbye"`, |
| wantErr: `unexpected EOF`, |
| }, { |
| in: `{"hello":"goodbye"]`, |
| wantErr: `invalid character ']', expected '}' at end of object`, |
| }, { |
| in: `{"hello":"goodbye"}`, |
| wantVal: V(Obj{{V("hello"), V("goodbye")}}), |
| wantOut: `{"hello":"goodbye"}`, |
| wantOutIndent: "{\n\t\"hello\": \"goodbye\"\n}", |
| }, { |
| in: `{"hello":"goodbye",}`, |
| wantErr: `invalid character '}' at start of string`, |
| }, { |
| in: `{"k":"v1","k":"v2"}`, |
| wantVal: V(Obj{ |
| {V("k"), V("v1")}, {V("k"), V("v2")}, |
| }), |
| wantOut: `{"k":"v1","k":"v2"}`, |
| wantOutIndent: "{\n\t\"k\": \"v1\",\n\t\"k\": \"v2\"\n}", |
| }, { |
| in: `{"k":{"k":{"k":"v"}}}`, |
| wantVal: V(Obj{ |
| {V("k"), V(Obj{ |
| {V("k"), V(Obj{ |
| {V("k"), V("v")}, |
| })}, |
| })}, |
| }), |
| wantOut: `{"k":{"k":{"k":"v"}}}`, |
| wantOutIndent: "{\n\t\"k\": {\n\t\t\"k\": {\n\t\t\t\"k\": \"v\"\n\t\t}\n\t}\n}", |
| }, { |
| in: `{"k":{"k":{"k":"v1","k":"v2"}}}`, |
| wantVal: V(Obj{ |
| {V("k"), V(Obj{ |
| {V("k"), V(Obj{ |
| {V("k"), V("v1")}, |
| {V("k"), V("v2")}, |
| })}, |
| })}, |
| }), |
| wantOut: `{"k":{"k":{"k":"v1","k":"v2"}}}`, |
| wantOutIndent: "{\n\t\"k\": {\n\t\t\"k\": {\n\t\t\t\"k\": \"v1\",\n\t\t\t\"k\": \"v2\"\n\t\t}\n\t}\n}", |
| }, { |
| in: " x", |
| wantErr: `syntax error (line 1:3)`, |
| }, { |
| in: `["💩"x`, |
| wantErr: `syntax error (line 1:5)`, |
| }, { |
| in: "\n\n[\"🔥🔥🔥\"x", |
| wantErr: `syntax error (line 3:7)`, |
| }, { |
| in: `["👍🏻👍🏿"x`, |
| wantErr: `syntax error (line 1:8)`, // multi-rune emojis; could be column:6 |
| }, { |
| in: "\"\x00\"", |
| wantErr: `invalid character '\x00' in string`, |
| }, { |
| in: "\"\xff\"", |
| wantErr: `invalid UTF-8 detected`, |
| wantVal: V(string("\xff")), |
| }, { |
| in: `"` + string(utf8.RuneError) + `"`, |
| wantVal: V(string(utf8.RuneError)), |
| wantOut: `"` + string(utf8.RuneError) + `"`, |
| }, { |
| in: `"\uFFFD"`, |
| wantVal: V(string(utf8.RuneError)), |
| wantOut: `"` + string(utf8.RuneError) + `"`, |
| }, { |
| in: `"\x"`, |
| wantErr: `invalid escape code "\\x" in string`, |
| }, { |
| in: `"\uXXXX"`, |
| wantErr: `invalid escape code "\\uXXXX" in string`, |
| }, { |
| in: `"\uDEAD"`, // unmatched surrogate pair |
| wantErr: `unexpected EOF`, |
| }, { |
| in: `"\uDEAD\uBEEF"`, // invalid surrogate half |
| wantErr: `invalid escape code "\\uBEEF" in string`, |
| }, { |
| in: `"\uD800\udead"`, // valid surrogate pair |
| wantVal: V("𐊭"), |
| wantOut: `"𐊭"`, |
| }, { |
| in: `"\u0000\"\\\/\b\f\n\r\t"`, |
| wantVal: V("\u0000\"\\/\b\f\n\r\t"), |
| wantOut: `"\u0000\"\\/\b\f\n\r\t"`, |
| }, { |
| in: `-`, |
| wantErr: `invalid "-" as number`, |
| }, { |
| in: `-0`, |
| wantVal: V(math.Copysign(0, -1)), |
| wantOut: `-0`, |
| }, { |
| in: `+0`, |
| wantErr: `invalid "+0" as value`, |
| }, { |
| in: `-+`, |
| wantErr: `invalid "-+" as number`, |
| }, { |
| in: `0.`, |
| wantErr: `invalid "0." as number`, |
| }, { |
| in: `.1`, |
| wantErr: `invalid ".1" as value`, |
| }, { |
| in: `0.e1`, |
| wantErr: `invalid "0.e1" as number`, |
| }, { |
| in: `0.0`, |
| wantVal: V(0.0), |
| wantOut: "0", |
| }, { |
| in: `01`, |
| wantErr: `invalid "01" as number`, |
| }, { |
| in: `0e`, |
| wantErr: `invalid "0e" as number`, |
| }, { |
| in: `0e0`, |
| wantVal: V(0.0), |
| wantOut: "0", |
| }, { |
| in: `0E0`, |
| wantVal: V(0.0), |
| wantOut: "0", |
| }, { |
| in: `0Ee`, |
| wantErr: `invalid "0Ee" as number`, |
| }, { |
| in: `-1.0E+1`, |
| wantVal: V(-10.0), |
| wantOut: "-10", |
| }, { |
| in: ` |
| { |
| "firstName" : "John", |
| "lastName" : "Smith" , |
| "isAlive" : true, |
| "age" : 27, |
| "address" : { |
| "streetAddress" : "21 2nd Street" , |
| "city" : "New York" , |
| "state" : "NY" , |
| "postalCode" : "10021-3100" |
| }, |
| "phoneNumbers" : [ |
| { |
| "type" : "home" , |
| "number" : "212 555-1234" |
| } , |
| { |
| "type" : "office" , |
| "number" : "646 555-4567" |
| } , |
| { |
| "type" : "mobile" , |
| "number" : "123 456-7890" |
| } |
| ], |
| "children" : [] , |
| "spouse" : null |
| } |
| `, |
| wantVal: V(Obj{ |
| {V("firstName"), V("John")}, |
| {V("lastName"), V("Smith")}, |
| {V("isAlive"), V(true)}, |
| {V("age"), V(27.0)}, |
| {V("address"), V(Obj{ |
| {V("streetAddress"), V("21 2nd Street")}, |
| {V("city"), V("New York")}, |
| {V("state"), V("NY")}, |
| {V("postalCode"), V("10021-3100")}, |
| })}, |
| {V("phoneNumbers"), V(Arr{ |
| V(Obj{ |
| {V("type"), V("home")}, |
| {V("number"), V("212 555-1234")}, |
| }), |
| V(Obj{ |
| {V("type"), V("office")}, |
| {V("number"), V("646 555-4567")}, |
| }), |
| V(Obj{ |
| {V("type"), V("mobile")}, |
| {V("number"), V("123 456-7890")}, |
| }), |
| })}, |
| {V("children"), V(Arr{})}, |
| {V("spouse"), V(nil)}, |
| }), |
| wantOut: `{"firstName":"John","lastName":"Smith","isAlive":true,"age":27,"address":{"streetAddress":"21 2nd Street","city":"New York","state":"NY","postalCode":"10021-3100"},"phoneNumbers":[{"type":"home","number":"212 555-1234"},{"type":"office","number":"646 555-4567"},{"type":"mobile","number":"123 456-7890"}],"children":[],"spouse":null}`, |
| wantOutIndent: `{ |
| "firstName": "John", |
| "lastName": "Smith", |
| "isAlive": true, |
| "age": 27, |
| "address": { |
| "streetAddress": "21 2nd Street", |
| "city": "New York", |
| "state": "NY", |
| "postalCode": "10021-3100" |
| }, |
| "phoneNumbers": [ |
| { |
| "type": "home", |
| "number": "212 555-1234" |
| }, |
| { |
| "type": "office", |
| "number": "646 555-4567" |
| }, |
| { |
| "type": "mobile", |
| "number": "123 456-7890" |
| } |
| ], |
| "children": [], |
| "spouse": null |
| }`, |
| }} |
| |
| opts := cmp.Options{ |
| cmpopts.EquateEmpty(), |
| cmp.Transformer("", func(v Value) interface{} { |
| switch v.typ { |
| case 0: |
| return nil // special case so Value{} == Value{} |
| case Null: |
| return nil |
| case Bool: |
| return v.Bool() |
| case Number: |
| return v.Number() |
| case String: |
| return v.String() |
| case Array: |
| return v.Array() |
| case Object: |
| return v.Object() |
| default: |
| panic("invalid type") |
| } |
| }), |
| } |
| for _, tt := range tests { |
| t.Run("", func(t *testing.T) { |
| if tt.in != "" || tt.wantVal.Type() != 0 || tt.wantErr != "" { |
| gotVal, err := Unmarshal([]byte(tt.in)) |
| if err == nil { |
| if tt.wantErr != "" { |
| t.Errorf("Unmarshal(): got nil error, want %v", tt.wantErr) |
| } |
| } else { |
| if tt.wantErr == "" { |
| t.Errorf("Unmarshal(): got %v, want nil error", err) |
| } else if !strings.Contains(err.Error(), tt.wantErr) { |
| t.Errorf("Unmarshal(): got %v, want %v", err, tt.wantErr) |
| } |
| } |
| if diff := cmp.Diff(gotVal, tt.wantVal, opts); diff != "" { |
| t.Errorf("Unmarshal(): output mismatch (-got +want):\n%s", diff) |
| } |
| } |
| if tt.wantOut != "" { |
| gotOut, err := Marshal(tt.wantVal, "") |
| if err != nil { |
| t.Errorf("Marshal(): got %v, want nil error", err) |
| } |
| if string(gotOut) != tt.wantOut { |
| t.Errorf("Marshal():\ngot: %s\nwant: %s", gotOut, tt.wantOut) |
| } |
| } |
| if tt.wantOutIndent != "" { |
| gotOut, err := Marshal(tt.wantVal, "\t") |
| if err != nil { |
| t.Errorf("Marshal(Indent): got %v, want nil error", err) |
| } |
| if string(gotOut) != tt.wantOutIndent { |
| t.Errorf("Marshal(Indent):\ngot: %s\nwant: %s", gotOut, tt.wantOutIndent) |
| } |
| } |
| }) |
| } |
| } |