Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
cfiderer opened this issue May 3, 2024 · 4 comments
Closed

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

cfiderer opened this issue May 3, 2024 · 4 comments

Comments

@cfiderer
Copy link

cfiderer commented May 3, 2024

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.

@cfiderer
Copy link
Author

cfiderer commented May 3, 2024

PS: Marshalling o2 or &o2 doesn't make a difference.

@seankhliao
Copy link
Member

This is working as expected due to how methods for embedded types are promoted

@seankhliao seankhliao closed this as not planned Won't fix, can't repro, duplicate, stale May 3, 2024
@cfiderer
Copy link
Author

cfiderer commented May 3, 2024

Why does a MarshalJSON() method for a part of a struct affect the marshalling of other parts of that struct?

Can you please provide a reference/link to the documentation that describes this behaviour?

@seankhliao
Copy link
Member

Unlike many projects, the Go project does not use GitHub Issues for general discussion or asking questions. GitHub Issues are used for tracking bugs and proposals only.

For questions please refer to https://github.com/golang/go/wiki/Questions

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants