Skip to content

Commit

Permalink
Merge pull request #1232 from tdakkota/fix/duplicate-discriminator
Browse files Browse the repository at this point in the history
fix(gen): encode discriminator field only once
  • Loading branch information
ernado authored May 3, 2024
2 parents b41f78c + b1ddda2 commit cbb350f
Show file tree
Hide file tree
Showing 11 changed files with 3,640 additions and 161 deletions.
1,612 changes: 1,565 additions & 47 deletions examples/ex_gotd/oas_json_gen.go

Large diffs are not rendered by default.

1,598 changes: 1,554 additions & 44 deletions examples/ex_telegram/oas_json_gen.go

Large diffs are not rendered by default.

41 changes: 23 additions & 18 deletions gen/_template/json/encoders_struct.tmpl
Original file line number Diff line number Diff line change
@@ -1,11 +1,31 @@
{{- define "json/encode_struct_fields" }}
{{- /*gotype: github.com/ogen-go/ogen/gen/ir.JSONFields*/ -}}
{{- range $i, $f := $ }}
{{- /*gotype: github.com/ogen-go/ogen/gen/ir.JSON*/ -}}
{{- $fields := $.Fields }}
{{- $additional := $.AdditionalProps }}
{{- $pattern := $.PatternProps }}
{{- $sum := $.SumProps }}

{{- range $i, $f := $fields }}
{
{{- $elem := field_elem $f }}
{{- template "json/enc" $elem }}
}
{{- end }}
{{- if $additional }}
for k, elem := range s.{{ $additional.Name }} {
e.FieldStart(k)
{{ template "json/enc" map_elem $additional.Type.Item }}
}
{{- end }}
{{- range $p := $pattern }}
for k, elem := range s.{{ $p.Name }} {
e.FieldStart(k)
{{ template "json/enc" map_elem $p.Type.Item }}
}
{{- end }}
{{- range $s := $sum }}
s.{{ $s.Name }}.encodeFields(e)
{{- end }}
{{- end }}

{{- define "json/encoders_struct" }}
Expand Down Expand Up @@ -67,22 +87,7 @@ func (s *{{ $.Name }}) Decode(d *jx.Decoder) error {
{{- else }}
// encodeFields encodes fields.
func (s {{ $.ReadOnlyReceiver }}) encodeFields(e *jx.Encoder) {
{{- template "json/encode_struct_fields" $fields }}
{{- if $additional }}
for k, elem := range s.{{ $additional.Name }} {
e.FieldStart(k)
{{ template "json/enc" map_elem $additional.Type.Item }}
}
{{- end }}
{{- range $p := $pattern }}
for k, elem := range s.{{ $p.Name }} {
e.FieldStart(k)
{{ template "json/enc" map_elem $p.Type.Item }}
}
{{- end }}
{{- range $s := $sum }}
s.{{ $s.Name }}.encodeFields(e)
{{- end }}
{{- template "json/encode_struct_fields" $.JSON }}
}

var jsonFieldsNameOf{{ $.Name }} = [{{ len $fields }}]string{
Expand Down
8 changes: 7 additions & 1 deletion gen/_template/json/encoders_sum.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@ func (s {{ $.ReadOnlyReceiver }}) encodeFields(e *jx.Encoder) {
case {{ $m.Type.Name }}{{ $.Name }}:
e.FieldStart({{ quote $.SumSpec.Discriminator }})
e.Str({{ quote $m.Key }})
s.{{ $m.Type.Name }}.encodeFields(e)
{{- $j := $m.Type.JSON.Except $.SumSpec.Discriminator }}
{{- if $j.AnyFields }}
{
s := s.{{ $m.Type.Name }}
{{- template "json/encode_struct_fields" $j }}
}
{{- end }}
{{- end }}
}
{{- else if $d.Fields }}
Expand Down
31 changes: 25 additions & 6 deletions gen/ir/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,35 @@ func (t *Type) JSON() JSON {

// JSON specifies json encoding and decoding for Type.
type JSON struct {
t *Type
t *Type
except []string
}

type JSONFields []*Field
// AnyFields whether if type has any fields to encode.
func (j JSON) AnyFields() bool {
for _, f := range j.t.Fields {
if f.Inline != InlineNone {
return true
}

// NotEmpty whether field slice is not empty.
func (j JSONFields) NotEmpty() bool {
return len(j) != 0
t := f.Tag.JSON
if t != "" && !slices.Contains(j.except, t) {
return true
}
}
return false
}

// Except return JSON with filter by given properties.
func (j JSON) Except(set ...string) JSON {
return JSON{
t: j.t,
except: set,
}
}

type JSONFields []*Field

// FirstRequiredIndex returns first required field index.
//
// Or -1 if there is no required fields.
Expand Down Expand Up @@ -70,7 +89,7 @@ func (j JSONFields) RequiredMask() []uint8 {
// Fields return all fields of Type that should be encoded via json.
func (j JSON) Fields() (fields JSONFields) {
for _, f := range j.t.Fields {
if f.Tag.JSON == "" {
if t := f.Tag.JSON; t == "" || slices.Contains(j.except, t) {
continue
}
fields = append(fields, f)
Expand Down
26 changes: 26 additions & 0 deletions internal/integration/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
std "encoding/json"
"fmt"
"strconv"
"strings"
"testing"

"github.com/go-faster/jx"
Expand Down Expand Up @@ -49,6 +50,31 @@ func testEncode(t *testing.T, encoder json.Marshaler, expected string) {
}
require.True(t, std.Valid(e.Bytes()), string(e.Bytes()))
require.JSONEq(t, expected, string(e.Bytes()), "encoding result mismatch")
require.NoError(t, validProperties(jx.DecodeBytes(e.Bytes()), []string{"$"}))
}

func validProperties(d *jx.Decoder, path []string) error {
if tt := d.Next(); tt != jx.Object {
return d.Skip()
}

m := map[string]struct{}{}
return d.Obj(func(d *jx.Decoder, key string) error {
path = append(path, key)
defer func() {
path = path[:len(path)-1]
}()

if _, ok := m[key]; ok {
return fmt.Errorf("duplicate field %q (at %q)", key, strings.Join(path, "."))
}
m[key] = struct{}{}

if err := validProperties(d, path); err != nil {
return err
}
return nil
})
}

func TestJSONGenerics(t *testing.T) {
Expand Down
97 changes: 88 additions & 9 deletions internal/integration/sample_api/oas_json_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit cbb350f

Please sign in to comment.