Repository: goccy/go-json
Pull request #264 (2021-07-05 11:33:27)

Fix encoding of indirect layout structure
fix https://github.com/goccy/go-json/issues/263


Patch:
diff --git a/internal/encoder/compiler.go b/internal/encoder/compiler.go
index f930efa..01a5d4b 100644
--- a/internal/encoder/compiler.go
+++ b/internal/encoder/compiler.go
@@ -1440,8 +1440,10 @@ func compileStruct(ctx *compileContext, isPtr bool) (*Opcode, error) {
 				// if parent is indirect type, set child indirect property to true
 				valueCode.Flags |= IndirectFlags
 			} else {
-				// if parent is not indirect type and child have only one field, set child indirect property to false
-				if i == 0 && valueCode.NextField != nil && valueCode.NextField.Op == OpStructEnd {
+				// if parent is not indirect type, set child indirect property to false.
+				// but if parent's indirect is false and isPtr is true, then indirect must be true.
+				// Do this only if indirectConversion is enabled at the end of compileStruct.
+				if i == 0 {
 					valueCode.Flags &= ^IndirectFlags
 				}
 			}
@@ -1544,7 +1546,11 @@ func compileStruct(ctx *compileContext, isPtr bool) (*Opcode, error) {
 	delete(ctx.structTypeToCompiledCode, typeptr)
 
 	if !disableIndirectConversion && (head.Flags&IndirectFlags == 0) && isPtr {
-		head.Flags |= IndirectFlags
+		headCode := head
+		for strings.Contains(headCode.Op.String(), "Head") {
+			headCode.Flags |= IndirectFlags
+			headCode = headCode.Next
+		}
 	}
 
 	return ret, nil


Test patch:
diff --git a/encode_test.go b/encode_test.go
index 6167f20..5b271b0 100644
--- a/encode_test.go
+++ b/encode_test.go
@@ -2004,3 +2004,32 @@ func TestInterfaceWithPointer(t *testing.T) {
 	}
 	assertEq(t, "interface{}", string(expected), string(actual))
 }
+
+func TestIssue263(t *testing.T) {
+	type Foo struct {
+		A []string `json:"a"`
+		B int      `json:"b"`
+	}
+
+	type MyStruct struct {
+		Foo *Foo `json:"foo,omitempty"`
+	}
+
+	s := MyStruct{
+		Foo: &Foo{
+			A: []string{"ls -lah"},
+			B: 0,
+		},
+	}
+	expected, err := stdjson.Marshal(s)
+	if err != nil {
+		t.Fatal(err)
+	}
+	actual, err := json.Marshal(s)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !bytes.Equal(expected, actual) {
+		t.Fatalf("expected:[%s] but got:[%s]", string(expected), string(actual))
+	}
+}
diff --git a/test/cover/cover_int_test.go b/test/cover/cover_int_test.go
index e76abb9..b21a528 100644
--- a/test/cover/cover_int_test.go
+++ b/test/cover/cover_int_test.go
@@ -631,6 +631,56 @@ func TestCoverInt(t *testing.T) {
 			}{A: -1}},
 		},
 
+		// HeadIntNotRootMultiFields
+		{
+			name: "HeadIntNotRootMultiFields",
+			data: struct {
+				A struct {
+					A int `json:"a"`
+					B int `json:"b"`
+				}
+			}{A: struct {
+				A int `json:"a"`
+				B int `json:"b"`
+			}{A: -1, B: 1}},
+		},
+		{
+			name: "HeadIntNotRootOmitEmptyMultiFields",
+			data: struct {
+				A struct {
+					A int `json:"a,omitempty"`
+					B int `json:"b,omitempty"`
+				}
+			}{A: struct {
+				A int `json:"a,omitempty"`
+				B int `json:"b,omitempty"`
+			}{A: -1, B: 1}},
+		},
+		{
+			name: "HeadIntNotRootStringMultiFields",
+			data: struct {
+				A struct {
+					A int `json:"a,string"`
+					B int `json:"b,string"`
+				}
+			}{A: struct {
+				A int `json:"a,string"`
+				B int `json:"b,string"`
+			}{A: -1, B: 1}},
+		},
+		{
+			name: "HeadIntNotRootStringOmitEmptyMultiFields",
+			data: struct {
+				A struct {
+					A int `json:"a,string,omitempty"`
+					B int `json:"b,string,omitempty"`
+				}
+			}{A: struct {
+				A int `json:"a,string,omitempty"`
+				B int `json:"b,string,omitempty"`
+			}{A: -1, B: 1}},
+		},
+
 		// HeadIntPtrNotRoot
 		{
 			name: "HeadIntPtrNotRoot",
@@ -791,6 +841,56 @@ func TestCoverInt(t *testing.T) {
 			}{A: -1})},
 		},
 
+		// PtrHeadIntNotRootMultiFields
+		{
+			name: "PtrHeadIntNotRootMultiFields",
+			data: struct {
+				A *struct {
+					A int `json:"a"`
+					B int `json:"b"`
+				}
+			}{A: &(struct {
+				A int `json:"a"`
+				B int `json:"b"`
+			}{A: -1, B: 1})},
+		},
+		{
+			name: "PtrHeadIntNotRootOmitEmptyMultiFields",
+			data: struct {
+				A *struct {
+					A int `json:"a,omitempty"`
+					B int `json:"b,omitempty"`
+				}
+			}{A: &(struct {
+				A int `json:"a,omitempty"`
+				B int `json:"b,omitempty"`
+			}{A: -1, B: 1})},
+		},
+		{
+			name: "PtrHeadIntNotRootStringMultiFields",
+			data: struct {
+				A *struct {
+					A int `json:"a,string"`
+					B int `json:"b,string"`
+				}
+			}{A: &(struct {
+				A int `json:"a,string"`
+				B int `json:"b,string"`
+			}{A: -1, B: 1})},
+		},
+		{
+			name: "PtrHeadIntNotRootStringOmitEmptyMultiFields",
+			data: struct {
+				A *struct {
+					A int `json:"a,string,omitempty"`
+					B int `json:"b,string,omitempty"`
+				}
+			}{A: &(struct {
+				A int `json:"a,string,omitempty"`
+				B int `json:"b,string,omitempty"`
+			}{A: -1, B: 1})},
+		},
+
 		// PtrHeadIntPtrNotRoot
 		{
 			name: "PtrHeadIntPtrNotRoot",