Skip to content

proposal: encoding/json: allow struct encoder to handle undefined fields #64515

@KumanekoSakura

Description

@KumanekoSakura

Proposal Details

I'm aware of #63397, but here is a simple approach which I want to use.

I want to use "struct" for defining API responses. But the API is so complicated that some of fields in that "struct" are conditionally initialized (i.e. needs to hide fields when values are not assigned). But due to golang's zero values, I can't hide fields in that "struct".

As a result, I'm currently using "map[string]any" instead of "struct" in order to make sure that only fields that have values defined (they might be golang's zero values) are included in the API responses. But since "struct" is more easy to understand, I want to make it possible to hide fields in a "struct" when values are not assigned.

The diff for json package and an example usage of this proposal are shown below.

--- a/json/encode.go
+++ b/json/encode.go
@@ -727,6 +727,10 @@ type structFields struct {
 	nameIndex map[string]int
 }
 
+type ValueDefinedTester interface {
+	IsDefined() bool
+}
+
 func (se structEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
 	next := byte('{')
 FieldLoop:
@@ -748,6 +752,9 @@ FieldLoop:
 		if f.omitEmpty && isEmptyValue(fv) {
 			continue
 		}
+		if vdt, ok := fv.Interface().(ValueDefinedTester); ok && !vdt.IsDefined() {
+			continue
+		}
 		e.WriteByte(next)
 		next = ','
 		if opts.escapeHTML {
package main

import (
	"encoding/json"
	"fmt"
)

type MyInt32 struct {
	intValue  int32
	isNull    bool
	isDefined bool
}

func (t MyInt32) IsDefined() bool {
	return t.isDefined
}

func (t MyInt32) MarshalJSON() ([]byte, error) {
	if t.isNull {
		return []byte("null"), nil
	} else {
		return []byte(fmt.Sprint(t.intValue)), nil
	}
}

func (t MyInt32) GetValue() int32 {
	return t.intValue
}

func (t *MyInt32) SetValue(v int32) {
	t.intValue = v
	t.isNull = false
	t.isDefined = true
}

func (t MyInt32) IsNull() bool {
	return t.isNull
}

func (t *MyInt32) SetNull() {
	t.intValue = 0
	t.isNull = true
	t.isDefined = true
}

func (t *MyInt32) Reset() {
	t.intValue = 0
	t.isNull = false
	t.isDefined = false
}

func main() {
	type MyStruct struct {
		I MyInt32
	}
	var s MyStruct
	s.I.SetValue(100) // => {"I":100}
	s.I.SetNull()     // => {"I":null}
	s.I.Reset()       // => {}
	if out, err := json.Marshal(s); err == nil {
		fmt.Println(string(out))
	}
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    Status

    Incoming

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions