package internalgengo import ( "fmt" "google.golang.org/protobuf/compiler/protogen" "google.golang.org/protobuf/reflect/protoreflect" ) var ( jwriterPackage = protogen.GoImportPath("github.com/mailru/easyjson/jwriter") jlexerPackage = protogen.GoImportPath("github.com/mailru/easyjson/jlexer") ) func emitJSONMethods(g *protogen.GeneratedFile, msg *protogen.Message) { emitJSONMarshal(g, msg) emitJSONUnmarshal(g, msg) } func emitJSONUnmarshal(g *protogen.GeneratedFile, msg *protogen.Message) { g.P("// UnmarshalJSON implements the json.Unmarshaler interface.") g.P("func (x *", msg.GoIdent.GoName, ") UnmarshalJSON(data []byte) error {") g.P("r := ", jlexerPackage.Ident("Lexer"), "{Data: data}") g.P("x.UnmarshalEasyJSON(&r)") g.P("return r.Error()") g.P("}") g.P("func (x *", msg.GoIdent.GoName, ") UnmarshalEasyJSON(in *", jlexerPackage.Ident("Lexer"), ") {") g.P("isTopLevel := in.IsStart()") g.P("if in.IsNull() {") g.P("if isTopLevel { in.Consumed() }") g.P("in.Skip()") g.P("return") g.P("}") g.P("in.Delim('{')") g.P("for !in.IsDelim('}') {") g.P("key := in.UnsafeFieldName(false)") g.P("in.WantColon()") g.P("if in.IsNull() { in.Skip(); in.WantComma(); continue }") g.P("switch key {") for _, f := range msg.Fields { g.P(`case "`, fieldJSONName(f), `":`) if f.Oneof != nil { g.P("xx := new(", f.GoIdent, ")") g.P("x." + f.Oneof.GoName + " = xx") emitJSONFieldRead(g, f, "xx") continue } emitJSONFieldRead(g, f, "x") } g.P("}") g.P("in.WantComma()") g.P("}") g.P("in.Delim('}')") g.P("if isTopLevel { in.Consumed() }") g.P("}") } func emitJSONParseInteger(g *protogen.GeneratedFile, ident string, method string, bitSize int, typ string) { g.P("r := in.JsonNumber()") g.P("n := r.String()") g.P("v, err := ", strconvPackage.Ident(method), "(n, 10, ", bitSize, ")") g.P("if err != nil {") g.P(" in.AddError(err)") g.P(" return") g.P("}") g.P(ident, " := ", typ, "(v)") } func emitJSONReadEnum(g *protogen.GeneratedFile, name string, enumType string) { g.P(`switch v := in.Interface().(type) { case string: if vv, ok := `+enumType+`_value[v]; ok { `+name+` = `+enumType+`(vv) break } vv, err := `, strconvPackage.Ident("ParseInt"), `(v, 10, 32) if err != nil { in.AddError(err) return } `+name+` = `+enumType+`(vv) case float64: `+name+` = `+enumType+`(v) }`) } func emitJSONFieldRead(g *protogen.GeneratedFile, f *protogen.Field, name string) { g.P("{") defer g.P("}") if f.Desc.IsList() { g.P("var f ", fieldType(g, f)[2:]) g.P("var list ", fieldType(g, f)) g.P("in.Delim('[')") defer g.P("in.Delim(']')") g.P("for !in.IsDelim(']') {") } else { g.P("var f ", fieldType(g, f)) } var template string switch f.Desc.Kind() { case protoreflect.BoolKind: template = "%s = in.Bool()" case protoreflect.EnumKind: g.Import(strconvPackage) enumType := fieldType(g, f).String() g.P("var parsedValue " + enumType) emitJSONReadEnum(g, "parsedValue", enumType) template = "%s = parsedValue" case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind: emitJSONParseInteger(g, "pv", "ParseInt", 32, "int32") template = "%s = pv" case protoreflect.Uint32Kind, protoreflect.Fixed32Kind: emitJSONParseInteger(g, "pv", "ParseUint", 32, "uint32") template = "%s = pv" case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind: emitJSONParseInteger(g, "pv", "ParseInt", 64, "int64") template = "%s = pv" case protoreflect.Uint64Kind, protoreflect.Fixed64Kind: emitJSONParseInteger(g, "pv", "ParseUint", 64, "uint64") template = "%s = pv" case protoreflect.FloatKind: template = "%s = in.Float32()" case protoreflect.DoubleKind: template = "%s = in.Float64()" case protoreflect.StringKind: template = "%s = in.String()" case protoreflect.BytesKind: // Since some time ago proto3 support optional keyword, thus the presence is not tracked by default: // https://github.com/protocolbuffers/protobuf-go/blob/fb995f184a1719ec42b247a3771d1036d92adf67/internal/impl/message_reflect_field.go#L327 // We do not explicitly support `optional` keyword, protoc will fail on such fileds. // Thus, treat empty string as `[]byte(nil)`. template = `{ tmp := in.Bytes() if len(tmp) == 0 { tmp = nil } %s = tmp }` case protoreflect.MessageKind: if f.Desc.IsList() { g.P("f = ", fieldType(g, f)[2:], "{}") } else { g.P("f = new(", fieldType(g, f)[1:], ")") } template = "%s.UnmarshalEasyJSON(in)" case protoreflect.GroupKind: panic("unimplemented") } g.P(fmt.Sprintf(template, "f")) if f.Desc.IsList() { g.P("list = append(list, f)") g.P("in.WantComma()") g.P("}") g.P(name, ".", f.GoName, " = list") } else { g.P(name, ".", f.GoName, " = f") } } func emitJSONMarshal(g *protogen.GeneratedFile, msg *protogen.Message) { g.P("// MarshalJSON implements the json.Marshaler interface.") g.P("func (x *", msg.GoIdent.GoName, ") MarshalJSON() ([]byte, error) {") g.P("w := ", jwriterPackage.Ident("Writer"), "{}") g.P("x.MarshalEasyJSON(&w)") g.P("return w.Buffer.BuildBytes(), w.Error") g.P("}") g.P("func (x *", msg.GoIdent.GoName, ") MarshalEasyJSON(out *", jwriterPackage.Ident("Writer"), ") {") g.P(`if x == nil { out.RawString("null"); return }`) if len(msg.Fields) != 0 { g.P("first := true") } g.P("out.RawByte('{')") for _, f := range msg.Fields { if f.Oneof != nil { if f.Oneof.Fields[0] != f { continue } g.P("switch xx := x.", f.Oneof.GoName, ".(type) {") for _, ff := range f.Oneof.Fields { g.P("case *", ff.GoIdent, ":") emitJSONFieldWrite(g, ff, "xx") } g.P("}") continue } emitJSONFieldWrite(g, f, "x") } g.P("out.RawByte('}')") g.P("}") } func emitJSONFieldWrite(g *protogen.GeneratedFile, f *protogen.Field, name string) { g.P("{") defer g.P("}") selector := name + "." + f.GoName // This code is responsible for ignoring default values. // We will restore it after having parametrized JSON marshaling. // // isNotDefault := notNil // if f.Desc.IsList() { // isNotDefault = notEmpty // } else if f.Desc.Kind() != protoreflect.MessageKind { // _, isNotDefault = easyprotoKindInfo(f.Desc.Kind()) // } // g.P("if ", isNotDefault(selector), "{") // defer g.P("}") g.P("if !first { out.RawByte(','); } else { first = false; }") g.P("const prefix string = ", `"\"`, fieldJSONName(f), `\":"`) g.P("out.RawString(prefix)") if f.Desc.IsList() { selector += "[i]" g.P("out.RawByte('[')") defer g.P("out.RawByte(']')") g.P("for i := range ", name, ".", f.GoName, " {") g.P("if i != 0 { out.RawByte(',') }") defer g.P("}") } var template string switch f.Desc.Kind() { case protoreflect.BoolKind: template = "out.Bool(%s)" case protoreflect.EnumKind: enumType := fieldType(g, f).String() template = `v := int32(%s) if vv, ok := ` + enumType + `_name[v]; ok { out.String(vv) } else { out.Int32(v) }` case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind: template = "out.Int32(%s)" case protoreflect.Uint32Kind, protoreflect.Fixed32Kind: template = "out.Uint32(%s)" case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind: g.P("out.RawByte('\"')") g.P("out.Buffer.Buf = ", strconvPackage.Ident("AppendInt"), "(out.Buffer.Buf, ", selector, ", 10)") g.P("out.RawByte('\"')") return case protoreflect.Uint64Kind, protoreflect.Fixed64Kind: g.P("out.RawByte('\"')") g.P("out.Buffer.Buf = ", strconvPackage.Ident("AppendUint"), "(out.Buffer.Buf, ", selector, ", 10)") g.P("out.RawByte('\"')") return case protoreflect.FloatKind: template = "out.Float32(%s)" case protoreflect.DoubleKind: template = "out.Float64(%s)" case protoreflect.StringKind: template = "out.String(%s)" case protoreflect.BytesKind: g.P("if ", selector, "!= nil {") g.P("out.Base64Bytes(", selector, ")") g.P("} else { out.String(\"\") }") return case protoreflect.MessageKind: template = "%s.MarshalEasyJSON(out)" case protoreflect.GroupKind: panic("unimplemented") } g.P(fmt.Sprintf(template, selector)) } func fieldJSONName(f *protogen.Field) string { if f.Desc.HasJSONName() { return f.Desc.JSONName() } return string(f.Desc.Name()) }