Skip to content

Bug: Panic when marshaling recursive struct with map[string]any field #559

@ilxqx

Description

@ilxqx

Summary

goccy/go-json panics when marshaling a recursive struct that contains a map[string]any field along with a slice of the same struct type (recursive children).

Environment

  • Go version: go version go1.25.3 darwin/arm64
  • goccy/go-json version: v0.10.5
  • OS: macOS (Darwin 25.0.0)

Expected Behavior

The library should successfully marshal the struct, just like the standard library encoding/json.

Actual Behavior

The library panics with:

  1. panic: runtime error: invalid memory address or nil pointer dereference (in some cases)
  2. panic: runtime error: slice bounds out of range [206158430215:145] (in other cases)

Minimal Reproduction Code

package main

import (
	"encoding/json"
	"fmt"

	goccyjson "github.com/goccy/go-json"
)

type Node struct {
	Label    string         `json:"label"`
	Value    string         `json:"value"`
	Meta     map[string]any `json:"meta,omitempty"`
	Children []Node         `json:"children,omitempty"`
}

func main() {
	// This panics with goccy/go-json but works fine with encoding/json
	tree := []Node{
		{
			Label: "Parent",
			Value: "p1",
			Meta: map[string]any{
				"code": "parent",
				"desc": "Parent node",
			},
			Children: []Node{
				{
					Label: "Child1",
					Value: "c1",
					Meta: map[string]any{
						"code": "child1",
						"desc": "First child",
					},
				},
			},
		},
	}

	// Standard library - works fine
	stdResult, stdErr := json.Marshal(tree)
	if stdErr != nil {
		fmt.Printf("stdlib error: %v\n", stdErr)
	} else {
		fmt.Printf("stdlib success: %d bytes\n", len(stdResult))
	}

	// goccy/go-json - PANICS
	goccyResult, goccyErr := goccyjson.Marshal(tree)
	if goccyErr != nil {
		fmt.Printf("goccy error: %v\n", goccyErr)
	} else {
		fmt.Printf("goccy success: %d bytes\n", len(goccyResult))
	}
}

Steps to Reproduce

  1. Save the code above to main.go
  2. Run go mod init test && go get github.com/goccy/[email protected]
  3. Run go run main.go
  4. Observe the panic

Stack Trace

Panic 1 (nil pointer dereference):

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x5 pc=0x102405b14]

goroutine 1 [running]:
github.com/goccy/go-json/internal/encoder/vm.ptrToString(...)
	.../internal/encoder/vm/util.go:89
github.com/goccy/go-json/internal/encoder/vm.Run(0x140001804e0, {0x140001bc400?, 0x0?, 0x400?}, 0x14000194070?)
	.../internal/encoder/vm/vm.go:1718 +0x4754
github.com/goccy/go-json.encodeRunCode(...)
	.../encode.go:310 +0x64
github.com/goccy/go-json.encode(...)
	.../encode.go:235 +0x1e4
github.com/goccy/go-json.marshal(...)
	.../encode.go:150 +0xb8
github.com/goccy/go-json.Marshal(...)
	.../json.go:170 +0x30

Panic 2 (slice bounds out of range):

panic: runtime error: slice bounds out of range [206158430215:145]

goroutine 1 [running]:
github.com/goccy/go-json/internal/encoder/vm.Run(0x140001a6680, {0x140001d8000?, 0x140001a6410?, 0x18?}, 0x18?)
	.../internal/encoder/vm/vm.go:440 +0x21d44
github.com/goccy/go-json.encodeRunCode(...)
	.../encode.go:310 +0x64
github.com/goccy/go-json.encode(...)
	.../encode.go:235 +0x1e4
github.com/goccy/go-json.marshal(...)
	.../encode.go:150 +0xb8
github.com/goccy/go-json.Marshal(...)
	.../json.go:170 +0x30

Analysis

The bug appears when all of the following conditions are met:

  1. Struct has a map[string]any field
  2. Struct has a recursive field (e.g., []Node)
  3. Both the parent and children nodes have non-empty Meta maps

Removing any of these conditions makes the panic disappear:

  • ✅ Works: Struct without Meta field
  • ✅ Works: Struct with Meta but without Children
  • ❌ Panics: Struct with both Meta and Children containing Meta

Workaround

For now, switching back to the standard library encoding/json resolves the issue:

import "encoding/json"

result, err := json.Marshal(tree)  // Use stdlib instead

Additional Context

This bug was discovered while implementing a tree options API that returns hierarchical data structures with metadata. The same code works perfectly with encoding/json but consistently panics with goccy/go-json.

The issue seems to be in the encoder VM's handling of recursive structures combined with map[string]any fields, possibly related to pointer management or slice indexing calculations in the encoder code generation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions