Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 75 additions & 1 deletion encode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2167,4 +2167,78 @@ func TestIssue290(t *testing.T) {
if !bytes.Equal(expected, got) {
t.Fatalf("failed to encode non empty interface. expected = %q but got %q", expected, got)
}
}
}

func TestIssue299(t *testing.T) {
t.Run("conflict second field", func(t *testing.T) {
type Embedded struct {
ID string `json:"id"`
Name map[string]string `json:"name"`
}
type Container struct {
Embedded
Name string `json:"name"`
}
c := &Container{
Embedded: Embedded{
ID: "1",
Name: map[string]string{"en": "Hello", "es": "Hola"},
},
Name: "Hi",
}
expected, _ := stdjson.Marshal(c)
got, err := json.Marshal(c)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(expected, got) {
t.Fatalf("expected %q but got %q", expected, got)
}
})
t.Run("conflict map field", func(t *testing.T) {
type Embedded struct {
Name map[string]string `json:"name"`
}
type Container struct {
Embedded
Name string `json:"name"`
}
c := &Container{
Embedded: Embedded{
Name: map[string]string{"en": "Hello", "es": "Hola"},
},
Name: "Hi",
}
expected, _ := stdjson.Marshal(c)
got, err := json.Marshal(c)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(expected, got) {
t.Fatalf("expected %q but got %q", expected, got)
}
})
t.Run("conflict slice field", func(t *testing.T) {
type Embedded struct {
Name []string `json:"name"`
}
type Container struct {
Embedded
Name string `json:"name"`
}
c := &Container{
Embedded: Embedded{
Name: []string{"Hello"},
},
Name: "Hi",
}
expected, _ := stdjson.Marshal(c)
got, err := json.Marshal(c)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(expected, got) {
t.Fatalf("expected %q but got %q", expected, got)
}
})
}
103 changes: 28 additions & 75 deletions internal/encoder/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -1102,97 +1102,50 @@ func structField(ctx *compileContext, fieldCode *Opcode, valueCode *Opcode, tag
return fieldCode
}

func isNotExistsField(head *Opcode) bool {
if head == nil {
return false
}
if head.Op != OpStructHead {
return false
}
if (head.Flags & AnonymousHeadFlags) == 0 {
return false
}
if head.Next == nil {
return false
}
if head.NextField == nil {
return false
}
if head.NextField.Op != OpStructAnonymousEnd {
return false
}
if head.Next.Op == OpStructAnonymousEnd {
return true
}
if head.Next.Op.CodeType() != CodeStructField {
return false
}
return isNotExistsField(head.Next)
type structFieldPair struct {
prevField *Opcode
curField *Opcode
isTaggedKey bool
linked bool
}

func optimizeAnonymousFields(head *Opcode) {
code := head
var prev *Opcode
func filterAnonymousStructFieldsByTags(value *Opcode, tags runtime.StructTags) *Opcode {
head := value
curField := head
removedFields := map[*Opcode]struct{}{}
for {
if code.Op == OpStructEnd {
break
for curField != nil {
existsKey := tags.ExistsKey(curField.DisplayKey)
if !existsKey || curField.Next.IsRecursiveOp() {
curField = curField.NextField
continue
}
if code.Op == OpStructField {
codeType := code.Next.Op.CodeType()
if codeType == CodeStructField {
if isNotExistsField(code.Next) {
code.Next = code.NextField
diff := code.Next.DisplayIdx - code.DisplayIdx
for i := uint32(0); i < diff; i++ {
code.Next.decOpcodeIndex()
}
linkPrevToNextField(code, removedFields)
code = prev
}
}
diff := curField.NextField.DisplayIdx - curField.DisplayIdx
for i := uint32(0); i < diff; i++ {
curField.NextField.decOpcodeIndex()
}
prev = code
code = code.NextField
if curField.IsStructHeadOp() || head == curField {
head = curField.NextField
} else {
linkPrevToNextField(curField, removedFields)
}
curField = curField.NextField
}
return head
}

type structFieldPair struct {
prevField *Opcode
curField *Opcode
isTaggedKey bool
linked bool
}

func anonymousStructFieldPairMap(tags runtime.StructTags, named string, valueCode *Opcode) map[string][]structFieldPair {
func anonymousStructFieldPairMap(named string, valueCode *Opcode) map[string][]structFieldPair {
anonymousFields := map[string][]structFieldPair{}
f := valueCode
var prevAnonymousField *Opcode
removedFields := map[*Opcode]struct{}{}
for {
existsKey := tags.ExistsKey(f.DisplayKey)
isHeadOp := strings.Contains(f.Op.String(), "Head")
if existsKey && f.Next != nil && strings.Contains(f.Next.Op.String(), "Recursive") {
// through
} else if isHeadOp && (f.Flags&AnonymousHeadFlags) == 0 {
if existsKey {
// TODO: need to remove this head
f.Op = OpStructHead
f.Flags |= AnonymousKeyFlags
f.Flags |= AnonymousHeadFlags
} else if named == "" {
if isHeadOp && (f.Flags&AnonymousHeadFlags) == 0 {
if named == "" {
f.Flags |= AnonymousHeadFlags
}
} else if named == "" && f.Op == OpStructEnd {
f.Op = OpStructAnonymousEnd
} else if existsKey {
diff := f.NextField.DisplayIdx - f.DisplayIdx
for i := uint32(0); i < diff; i++ {
f.NextField.decOpcodeIndex()
}
linkPrevToNextField(f, removedFields)
}

if f.DisplayKey == "" {
if f.NextField == nil {
break
Expand Down Expand Up @@ -1422,7 +1375,8 @@ func compileStruct(ctx *compileContext, isPtr bool) (*Opcode, error) {
if tag.IsTaggedKey {
tagKey = tag.Key
}
for k, v := range anonymousStructFieldPairMap(tags, tagKey, valueCode) {
valueCode = filterAnonymousStructFieldsByTags(valueCode, tags)
for k, v := range anonymousStructFieldPairMap(tagKey, valueCode) {
anonymousFields[k] = append(anonymousFields[k], v...)
}

Expand Down Expand Up @@ -1540,7 +1494,6 @@ func compileStruct(ctx *compileContext, isPtr bool) (*Opcode, error) {
head.End = structEndCode
code.Next = structEndCode
optimizeConflictAnonymousFields(anonymousFields)
optimizeAnonymousFields(head)
ret := (*Opcode)(unsafe.Pointer(head))
compiled.Code = ret

Expand Down
16 changes: 15 additions & 1 deletion internal/encoder/opcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,20 @@ type Opcode struct {
DisplayKey string // key text to display
}

func (c *Opcode) IsStructHeadOp() bool {
if c == nil {
return false
}
return strings.Contains(c.Op.String(), "Head")
}

func (c *Opcode) IsRecursiveOp() bool {
if c == nil {
return false
}
return strings.Contains(c.Op.String(), "Recursive")
}

func (c *Opcode) MaxIdx() uint32 {
max := uint32(0)
for _, value := range []uint32{
Expand Down Expand Up @@ -621,7 +635,7 @@ func linkPrevToNextField(cur *Opcode, removedFields map[*Opcode]struct{}) {
nextCode = code.Next
}
if nextCode == fcode {
code.Next = fcode.Next
code.Next = fcode.NextField
break
} else if nextCode.Op == OpEnd {
break
Expand Down