Skip to content

encoding/json: adding a MarshalJSON method confuses JSON marshalling #67154

Closed as not planned
@cfiderer

Description

@cfiderer

Go version

go version go1.22.2 darwin/arm64

Output of go env in your module/workspace:

GO111MODULE=''
GOARCH='arm64'
GOBIN=''
GOCACHE='/Users/user/Library/Caches/go-build'
GOENV='/Users/user/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMODCACHE='/Users/user/go/pkg/mod'
GONOPROXY='*.sap.corp'
GONOSUMDB='*.sap.corp'
GOOS='darwin'
GOPATH='/Users/user/go'
GOPRIVATE='*.sap.corp'
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/go'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/local/go/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.22.2'
GCCGO='gccgo'
AR='ar'
CC='clang'
CXX='clang++'
CGO_ENABLED='1'
GOMOD='/Users/user/SAPDevelop/issues/MarshalJson/go.mod'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/lc/5666d_g13j92hx9n3jdh5g2w0000gn/T/go-build1386616878=/tmp/go-build -gno-record-gcc-switches -fno-common'

What did you do?

I'm working with Kubernetes and custom resources. For logging and log filtering, I added a MarshalJSON() method to a struct field.

package main

import (
	"encoding/json"
	"fmt"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// Object1Meta wraps metav1.Object1Meta to control logging.
type Object1Meta struct {
	metav1.ObjectMeta
}


type MyObjectSpec struct {
	Value1 string
	Value2 string
	Secret string
}

type MyObjectStatus struct {
	Status string
	ErrMsg string
}

type MyObject1 struct {
	metav1.TypeMeta `json:",inline"`
	Object1Meta     `json:"metadata,omitempty"`
	Spec            MyObjectSpec   `json:"spec,omitempty"`
	Status          MyObjectStatus `json:"status,omitempty"`
}

// Object2Meta wraps metav1.Object1Meta to control logging like Object1Meta, but implements a MarshalJSON method.

type Object2Meta struct {
	metav1.ObjectMeta
}

func (om Object2Meta) MarshalJSON() ([]byte, error) {
	fields := make(map[string]interface{}, 4)

	fields["name"] = om.Name
	fields["namespace"] = om.Namespace
	fields["generation"] = om.Generation
	fields["creationTimestamp"] = &om.CreationTimestamp

	return json.Marshal(fields)
}

type MyObject2 struct {
	metav1.TypeMeta `json:",inline"`
	Object2Meta     `json:"metadata,omitempty"`
	Spec            MyObjectSpec   `json:"spec,omitempty"`
	Status          MyObjectStatus `json:"status,omitempty"`
}

func main() {
	now := metav1.NewTime(time.Now())

	o1 := MyObject1{
		TypeMeta: metav1.TypeMeta{
			Kind: "object1", APIVersion: "api/v1",
		},
		Object1Meta: Object1Meta{metav1.ObjectMeta{
			Name: "o1", Namespace: "ns1", Generation: 1234, CreationTimestamp: now,
		}},
		Spec: MyObjectSpec{
			Value1: "value1", Value2: "value2", Secret: "hidden",
		},
		Status: MyObjectStatus{
			Status: "ok",
		},
	}

	b1, _ := json.Marshal(o1)

	o2 := MyObject2{
		TypeMeta: metav1.TypeMeta{
			Kind: "object1", APIVersion: "api/v1",
		},
		Object2Meta: Object2Meta{metav1.ObjectMeta{
			Name: "o1", Namespace: "ns1", Generation: 1234, CreationTimestamp: now,
		}},
		Spec: MyObjectSpec{
			Value1: "value1", Value2: "value2", Secret: "hidden",
		},
		Status: MyObjectStatus{
			Status: "ok",
		},
	}

	b2, _ := json.Marshal(&o2)

	fmt.Println("b1", string(b1))
	fmt.Println("b2", string(b2))
}

Complete (vendored) source code: MarshalJson.zip

What did you see happen?

Calling json.Marshal()onMyObject2` generates only the output from MarshalJSON(), all other entities from o2 are missing.

b1 {"kind":"object1","apiVersion":"api/v1","metadata":{"name":"o1","namespace":"ns1","generation":1234,"creationTimestamp":"2024-05-03T09:40:38Z"},"spec":{"Value1":"value1","Value2":"value2","Secret":"hidden"},"status":{"Status":"ok","ErrMsg":""}}
b2 {"creationTimestamp":"2024-05-03T09:40:38Z","generation":1234,"name":"o1","namespace":"ns1"}

What did you expect to see?

I expect the two structures in my sample program to generate the same JSON output.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions