diff --git a/Makefile b/Makefile index 420f306..39d52e3 100644 --- a/Makefile +++ b/Makefile @@ -20,9 +20,11 @@ generate: root build .root/bin/easyjson -stubs \ .root/src/$(PKG)/tests/snake.go \ .root/src/$(PKG)/tests/data.go \ - .root/src/$(PKG)/tests/omitempty.go + .root/src/$(PKG)/tests/omitempty.go \ + .root/src/$(PKG)/tests/nothing.go .root/bin/easyjson -all .root/src/$(PKG)/tests/data.go + .root/bin/easyjson -all .root/src/$(PKG)/tests/nothing.go .root/bin/easyjson -snake_case .root/src/$(PKG)/tests/snake.go .root/bin/easyjson -omit_empty .root/src/$(PKG)/tests/omitempty.go .root/bin/easyjson -build_tags=use_easyjson .root/src/$(PKG)/benchmark/data.go diff --git a/bootstrap/bootstrap.go b/bootstrap/bootstrap.go index e9a1d21..04aaaad 100644 --- a/bootstrap/bootstrap.go +++ b/bootstrap/bootstrap.go @@ -50,20 +50,23 @@ func (g *Generator) writeStub() error { fmt.Fprintln(f, "// compilable during generation.") fmt.Fprintln(f) fmt.Fprintln(f, "package ", g.PkgName) - fmt.Fprintln(f) - fmt.Fprintln(f, "import (") - fmt.Fprintln(f, ` "`+pkgWriter+`"`) - fmt.Fprintln(f, ` "`+pkgLexer+`"`) - fmt.Fprintln(f, ")") + + if len(g.Types) > 0 { + fmt.Fprintln(f) + fmt.Fprintln(f, "import (") + fmt.Fprintln(f, ` "`+pkgWriter+`"`) + fmt.Fprintln(f, ` "`+pkgLexer+`"`) + fmt.Fprintln(f, ")") + } for _, t := range g.Types { fmt.Fprintln(f) if !g.NoStdMarshalers { - fmt.Fprintln(f, "func (*", t, ") MarshalJSON() ([]byte, error) { return nil, nil }") + fmt.Fprintln(f, "func (", t, ") MarshalJSON() ([]byte, error) { return nil, nil }") fmt.Fprintln(f, "func (*", t, ") UnmarshalJSON([]byte) error { return nil }") } - fmt.Fprintln(f, "func (*", t, ") MarshalEasyJSON(w *jwriter.Writer) {}") + fmt.Fprintln(f, "func (", t, ") MarshalEasyJSON(w *jwriter.Writer) {}") fmt.Fprintln(f, "func (*", t, ") UnmarshalEasyJSON(l *jlexer.Lexer) {}") fmt.Fprintln(f) fmt.Fprintln(f, "type EasyJSON_exporter_"+t+" *"+t) @@ -90,8 +93,10 @@ func (g *Generator) writeMain() (path string, err error) { fmt.Fprintln(f, ` "os"`) fmt.Fprintln(f) fmt.Fprintf(f, " %q\n", genPackage) - fmt.Fprintln(f) - fmt.Fprintf(f, " pkg %q\n", g.PkgPath) + if len(g.Types) > 0 { + fmt.Fprintln(f) + fmt.Fprintf(f, " pkg %q\n", g.PkgPath) + } fmt.Fprintln(f, ")") fmt.Fprintln(f) fmt.Fprintln(f, "func main() {") diff --git a/gen/generator.go b/gen/generator.go index b6dc08e..83bf812 100644 --- a/gen/generator.go +++ b/gen/generator.go @@ -156,7 +156,12 @@ func (g *Generator) printHeader() { fmt.Println(")") fmt.Println("") - fmt.Println("var _ = json.RawMessage{} // suppress unused package warning") + fmt.Println("// suppress unused package warning") + fmt.Println("var (") + fmt.Println(" _ = json.RawMessage{}") + fmt.Println(" _ = jlexer.Lexer{}") + fmt.Println(" _ = jwriter.Writer{}") + fmt.Println(")") fmt.Println() } @@ -222,13 +227,15 @@ func (g *Generator) pkgAlias(pkgPath string) string { // getType return the textual type name of given type that can be used in generated code. func (g *Generator) getType(t reflect.Type) string { - switch t.Kind() { - case reflect.Ptr: - return "*" + g.getType(t.Elem()) - case reflect.Slice: - return "[]" + g.getType(t.Elem()) - case reflect.Map: - return "map[" + g.getType(t.Key()) + "]" + g.getType(t.Elem()) + if t.Name() == "" { + switch t.Kind() { + case reflect.Ptr: + return "*" + g.getType(t.Elem()) + case reflect.Slice: + return "[]" + g.getType(t.Elem()) + case reflect.Map: + return "map[" + g.getType(t.Key()) + "]" + g.getType(t.Elem()) + } } if t.Name() == "" || t.PkgPath() == "" { diff --git a/jwriter/writer.go b/jwriter/writer.go index 85fa440..907675f 100644 --- a/jwriter/writer.go +++ b/jwriter/writer.go @@ -4,15 +4,15 @@ package jwriter import ( "io" "strconv" + "unicode/utf8" "github.com/mailru/easyjson/buffer" ) // Writer is a JSON writer. type Writer struct { - EscapeLtGt bool - Error error - Buffer buffer.Buffer + Error error + Buffer buffer.Buffer } // Size returns the size of the data that was written out. @@ -198,10 +198,7 @@ func (w *Writer) Bool(v bool) { } } -func hex(c byte) byte { - const chars = "0123456789abcdef" - return chars[c&0xf] -} +const chars = "0123456789abcdef" func (w *Writer) String(s string) { w.Buffer.AppendByte('"') @@ -211,41 +208,65 @@ func (w *Writer) String(s string) { p := 0 // last non-escape symbol - for i := 0; i < len(s); i++ { - c := s[i] - var escape byte - switch c { - case '\t': - escape = 't' - case '\r': - escape = 'r' - case '\n': - escape = 'n' - case '\\': - escape = '\\' - case '"': - escape = '"' - case '<', '>': - if !w.EscapeLtGt { - continue + for i := 0; i < len(s); { + // single-with character + if c := s[i]; c < utf8.RuneSelf { + var escape byte + switch c { + case '\t': + escape = 't' + case '\r': + escape = 'r' + case '\n': + escape = 'n' + case '\\': + escape = '\\' + case '"': + escape = '"' + case '<', '>': + // do nothing + default: + if c >= 0x20 { + // no escaping is required + i++ + continue + } } - default: - if c >= 0x20 { - // no escaping is required - continue + if escape != 0 { + w.Buffer.AppendString(s[p:i]) + w.Buffer.AppendByte('\\') + w.Buffer.AppendByte(escape) + } else { + w.Buffer.AppendString(s[p:i]) + w.Buffer.AppendString(`\u00`) + w.Buffer.AppendByte(chars[c>>4]) + w.Buffer.AppendByte(chars[c&0xf]) } + i++ + p = i + continue } - if escape != 0 { + + // broken utf + runeValue, runeWidth := utf8.DecodeRuneInString(s[i:]) + if runeValue == utf8.RuneError && runeWidth == 1 { w.Buffer.AppendString(s[p:i]) - w.Buffer.AppendByte('\\') - w.Buffer.AppendByte(escape) - } else { - w.Buffer.AppendString(s[p:i]) - w.Buffer.AppendString(`\u00`) - w.Buffer.AppendByte(hex(c >> 4)) - w.Buffer.AppendByte(hex(c)) + w.Buffer.AppendString(`\ufffd`) + i++ + p = i + continue } - p = i + 1 + + // jsonp stuff - tab separator and line separator + if runeValue == '\u2028' || runeValue == '\u2029' { + w.Buffer.AppendString(s[p:i]) + w.Buffer.AppendString(`\u202`) + w.Buffer.AppendByte(chars[runeValue&0xf]) + i += runeWidth + p = i + continue + } + i += runeWidth } w.Buffer.AppendString(s[p:]) w.Buffer.AppendByte('"') diff --git a/tests/basic_test.go b/tests/basic_test.go index f6fdc92..8525240 100644 --- a/tests/basic_test.go +++ b/tests/basic_test.go @@ -105,24 +105,28 @@ func TestParseNull(t *testing.T) { } } -var testCasesEncodeLtGt = []struct { - Writer *jwriter.Writer - Encoded string +var testSpecialCases = []struct { + EncodedString string + Value string }{ - {&jwriter.Writer{ - EscapeLtGt: false, - }, encodeLtGtFalseWantString}, - {&jwriter.Writer{ - EscapeLtGt: true, - }, encodeLtGtTrueWantString}, + {`"Username \u003cuser@example.com\u003e"`, `Username `}, + {`"Username\ufffd"`, "Username\xc5"}, + {`"тестzтест"`, "тестzтест"}, + {`"тест\ufffdтест"`, "тест\xc5тест"}, + {`"绿茶"`, "绿茶"}, + {`"绿\ufffd茶"`, "绿\xc5茶"}, + {`"тест\u2028"`, "тест\xE2\x80\xA8"}, + {`"\\\r\n\t\""`, "\\\r\n\t\""}, + {`"ü"`, "ü"}, } -func TestEncodeLtGt(t *testing.T) { - for i, test := range testCasesEncodeLtGt { - test.Writer.String(encodeLtGtString) - got := string(test.Writer.Buffer.BuildBytes()) - if got != test.Encoded { - t.Errorf("[%d] Encoded() = %+v; want %+v", i, got, test.Encoded) +func TestSpecialCases(t *testing.T) { + for i, test := range testSpecialCases { + w := jwriter.Writer{} + w.String(test.Value) + got := string(w.Buffer.BuildBytes()) + if got != test.EncodedString { + t.Errorf("[%d] Encoded() = %+v; want %+v", i, got, test.EncodedString) } } -} +} \ No newline at end of file diff --git a/tests/data.go b/tests/data.go index d0e1e7a..1378f89 100644 --- a/tests/data.go +++ b/tests/data.go @@ -436,41 +436,97 @@ var mapsString = `{` + `"CustomMap":{"c":"d"}` + `}` +type NamedSlice []Str +type NamedMap map[Str]Str + type DeepNest struct { - SliceMap map[Str][]Str - MapSlice []map[Str]Str + SliceMap map[Str][]Str + SliceMap1 map[Str][]Str + NamedSliceMap map[Str]NamedSlice + NamedMapMap map[Str]NamedMap + MapSlice []map[Str]Str + NamedSliceSlice []NamedSlice + NamedMapSlice []NamedMap + NamedStringSlice []NamedString } var deepNestValue = DeepNest{ SliceMap: map[Str][]Str{ - "testSliceMap1": []Str{ + "testSliceMap": []Str{ "0", "1", }, - "testSliceMap2": nil, + }, + SliceMap1: map[Str][]Str{ + "testSliceMap1": nil, + }, + NamedSliceMap: map[Str]NamedSlice{ + "testNamedSliceMap": NamedSlice{ + "2", + "3", + }, + }, + NamedMapMap: map[Str]NamedMap{ + "testNamedMapMap": NamedMap{ + "key1": "value1", + }, }, MapSlice: []map[Str]Str{ map[Str]Str{ - "testMapSlice1": "someValue", + "testMapSlice": "someValue", }, }, + NamedSliceSlice: []NamedSlice{ + NamedSlice{ + "someValue1", + "someValue2", + }, + NamedSlice{ + "someValue3", + "someValue4", + }, + }, + NamedMapSlice: []NamedMap{ + NamedMap{ + "key2": "value2", + }, + NamedMap{ + "key3": "value3", + }, + }, + NamedStringSlice: []NamedString{ + "value4", "value5", + }, } var deepNestString = `{` + `"SliceMap":{` + - `"testSliceMap1":["0","1"],` + - `"testSliceMap2":[]` + + `"testSliceMap":["0","1"]` + + `},` + + `"SliceMap1":{` + + `"testSliceMap1":[]` + + `},` + + `"NamedSliceMap":{` + + `"testNamedSliceMap":["2","3"]` + + `},` + + `"NamedMapMap":{` + + `"testNamedMapMap":{"key1":"value1"}` + `},` + `"MapSlice":[` + - `{"testMapSlice1":"someValue"}` + - `]` + + `{"testMapSlice":"someValue"}` + + `],` + + `"NamedSliceSlice":[` + + `["someValue1","someValue2"],` + + `["someValue3","someValue4"]` + + `],` + + `"NamedMapSlice":[` + + `{"key2":"value2"},` + + `{"key3":"value3"}` + + `],` + + `"NamedStringSlice":["value4","value5"]` + `}` type RequiredOptionalStruct struct { FirstName string `json:"first_name,required"` Lastname string `json:"last_name"` } - -var encodeLtGtString = `Username ` -var encodeLtGtFalseWantString = `"Username "` -var encodeLtGtTrueWantString = `"Username \u003cuser@example.com\u003e"` diff --git a/tests/nothing.go b/tests/nothing.go new file mode 100644 index 0000000..35334f5 --- /dev/null +++ b/tests/nothing.go @@ -0,0 +1,3 @@ +package tests + +// No structs in this file