JSON字段设置为null与字段不存在之间的区别是什么?

huangapple go评论77阅读模式
英文:

JSON field set to null vs field not there

问题

在golang中,有没有办法区分将JSON字段设置为null和在解组成结构体时JSON字段不存在的情况?因为两者都会将结构体中的值设置为nil,但我需要知道字段是否存在,并查看是否有人将其设置为null。

type MyStruct struct {
    SomeField1 string `json:"somefield1"`
    SomeField2 *string `json:"somefield2"`
}

func main() {
    jsonStr1 := `{"somefield1":"somevalue1","somefield2":null}`
    jsonStr2 := `{"somefield1":"somevalue1"}`

    var obj1 MyStruct
    var obj2 MyStruct

    err1 := json.Unmarshal([]byte(jsonStr1), &obj1)
    err2 := json.Unmarshal([]byte(jsonStr2), &obj2)

    if err1 != nil {
        fmt.Println("Error unmarshalling jsonStr1:", err1)
    } else {
        if obj1.SomeField2 != nil {
            fmt.Println("somefield2 exists and is set to null")
        } else {
            fmt.Println("somefield2 does not exist")
        }
    }

    if err2 != nil {
        fmt.Println("Error unmarshalling jsonStr2:", err2)
    } else {
        if obj2.SomeField2 != nil {
            fmt.Println("somefield2 exists and is set to null")
        } else {
            fmt.Println("somefield2 does not exist")
        }
    }
}

上述代码中,我们定义了一个结构体MyStruct,其中SomeField1是一个字符串字段,SomeField2是一个指向字符串的指针字段。在解组JSON时,如果SomeField2的值为null,则将其设置为nil。通过检查SomeField2是否为nil,我们可以确定字段是否存在并且是否被设置为null。

对于给定的两个JSON示例,jsonStr1中的somefield2存在且设置为null,而jsonStr2中的somefield2不存在。根据解组后的结果,我们可以确定这两种情况。

希望对你有所帮助!

英文:

Is there a way, in golang, to see if I can differentiate between a json field being set to null vs a json field not being there when unmarshalled into a struct? Because both set the value in the struct to be nil, but I need to know if the field was there to begin with and to see if someone set it to null.

{
  "somefield1":"somevalue1",
  "somefield2":null
}

VS

{
  "somefield1":"somevalue1",
}

Both jsons will be nil when unmarshalled into a struct.
Any useful resources will be very appreciated!

答案1

得分: 15

使用json.RawMessage来“延迟”解组过程,以确定在决定要执行某些操作之前的原始字节:

var data = []byte(`{
        "somefield1":"somevalue1",
        "somefield2": null
}`)

type Data struct {
    SomeField1 string          
    SomeField2 json.RawMessage
}

func main() {
    d := &Data{}
    
    _ = json.Unmarshal(data, &d)
    
    fmt.Println(d.SomeField1)
    
    if len(d.SomeField2) > 0 {
        if string(d.SomeField2) == "null" {
            fmt.Println("somefield2 is there but null")
        } else {
            fmt.Println("somefield2 is there and not null")
            // Do something with the data
        }
    } else {
        fmt.Println("somefield2 doesn't exist")
    }
}

请参见 playground https://play.golang.org/p/Wganpf4sbO

英文:

Use json.RawMessage to "delay" the unmarshaling process to determine the raw byte before deciding to do something:

var data = []byte(`{
        "somefield1":"somevalue1",
        "somefield2": null
}`)

type Data struct {
	SomeField1 string          
	SomeField2 json.RawMessage
}

func main() {
	d := &Data{}
	
	_ = json.Unmarshal(data, &d)
	
	fmt.Println(d.SomeField1)
	
	if len(d.SomeField2) > 0 {
        if string(d.SomeField2) == "null" {
		    fmt.Println("somefield2 is there but null")
        } else {
            fmt.Println("somefield2 is there and not null")
            // Do something with the data
        }
	} else {
        fmt.Println("somefield2 doesn't exist")
    }
}

See the playground https://play.golang.org/p/Wganpf4sbO

答案2

得分: 13

如果您使用的是 Go 1.18+,您可以使用一个简单的泛型结构来判断 JSON 值是否为 undefinednull

type Optional[T any] struct {
	Defined bool
	Value   *T
}

// UnmarshalJSON 通过委托给包装类型 (T) 来实现。
// 只有在 JSON 载荷中定义了该值时才会调用它。
func (o *Optional[T]) UnmarshalJSON(data []byte) error {
	o.Defined = true
	return json.Unmarshal(data, &o.Value)
}

然后,您可以在结构体中使用这个类型:

type Payload struct {
	Field1 Optional[string] `json:"field1"`
	Field2 Optional[bool]   `json:"field2"`
	Field3 Optional[int32]  `json:"field3"`
}

您可以使用 Defined 字段来判断字段是否为 nullundefined

请查看此 Playground 链接以获取完整示例:https://go.dev/play/p/JZfZyVVUABz

原始答案(泛型之前)

另一种方法是使用自定义类型:

// OptionalString 是一个表示 JSON 字符串的结构体,可以是未定义的(Defined == false)、空值(Value == nil && Defined == true)或具有字符串值的定义值
type OptionalString struct {
	Defined bool
	Value   *string
}

// UnmarshalJSON 实现了 json.Unmarshaler 接口。
// 当调用时,表示该值在 JSON 载荷中定义了。
func (os *OptionalString) UnmarshalJSON(data []byte) error {
	// 只有在键存在时才调用 UnmarshalJSON
	os.Defined = true
	return json.Unmarshal(data, &os.Value)
}


// Payload 表示您要表示的 JSON 载荷。
type Payload struct {
	SomeField1 string         `json:"somefield1"`
	SomeField2 OptionalString `json:"somefield2"`
}

然后,您可以使用常规的 json.Unmarshal 函数来获取您的值,例如:

	var p Payload
	_ = json.Unmarshal([]byte(`{
        	"somefield1":"somevalue1",
        	"somefield2":null
        }`), &p)
	fmt.Printf("Should be defined == true and value == nil: \n%+v\n\n", p)


	p = Payload{}
	_ = json.Unmarshal([]byte(`{"somefield1":"somevalue1"}`), &p)
	fmt.Printf("Should be defined == false \n%+v\n\n", p)
	
	p = Payload{}
	_ = json.Unmarshal([]byte(`{
        	"somefield1":"somevalue1",
        	"somefield2":"somevalue2"
        }`), &p)
	fmt.Printf("Parsed should be defined == true and value != nil \n%+v\n", p)
	if p.SomeField2.Value != nil {
	  fmt.Printf("SomeField2's value is %s", *p.SomeField2.Value)
	}

应该会输出:

Should be defined == true and value == nil: 
{SomeField1:"somevalue1" SomeField2:{Defined:true Value:<nil>}}

Should be defined == false 
{SomeField1:"somevalue1" SomeField2:{Defined:false Value:<nil>}}

Parsed should be defined == true and value != nil 
{SomeField1:"somevalue1" SomeField2:{Defined:true Value:0xc000010370}}
SomeField2's value is "somevalue2"

完整示例的 Playground 链接:https://play.golang.org/p/AUDwPKHBs62

请注意,您需要为要包装的每种类型创建一个结构体,因此,如果您需要一个可选的数字,您需要创建一个类似的 OptionalFloat64(所有 JSON 数字都可以是 64 位浮点数)结构体,并进行类似的实现。
如果/当 Go 支持泛型时,这可以简化为一个通用的结构体。

英文:

If you're on Go 1.18+ you can use a simple generic struct to know when a JSON value is undefined or null:

type Optional[T any] struct {
	Defined bool
	Value   *T
}

// UnmarshalJSON is implemented by deferring to the wrapped type (T).
// It will be called only if the value is defined in the JSON payload.
func (o *Optional[T]) UnmarshalJSON(data []byte) error {
	o.Defined = true
	return json.Unmarshal(data, &amp;o.Value)
}

That's all, you can then just use this type in your structs:

type Payload struct {
	Field1 Optional[string] `json:&quot;field1&quot;`
	Field2 Optional[bool]   `json:&quot;field2&quot;`
	Field3 Optional[int32]  `json:&quot;field3&quot;`
}

And you'll be able to use the Defined field to know if the field was null or undefined.

Check this playground link for a full example: https://go.dev/play/p/JZfZyVVUABz

Original answer (pre-generics)

Another way to do this, with a custom type:

// OptionalString is a struct that represents a JSON string that can be
// undefined (Defined == false), null (Value == nil &amp;&amp; Defined == true) or 
// defined with a string value
type OptionalString struct {
	Defined bool
	Value   *string
}

// UnmarshalJSON implements the json.Unmarshaler interface.
// When called, it means that the value is defined in the JSON payload.
func (os *OptionalString) UnmarshalJSON(data []byte) error {
	// UnmarshalJSON is called only if the key is present
	os.Defined = true
	return json.Unmarshal(data, &amp;os.Value)
}


// Payload represents the JSON payload that you want to represent.
type Payload struct {
	SomeField1 string         `json:&quot;somefield1&quot;`
	SomeField2 OptionalString `json:&quot;somefield2&quot;`
}

You can then just use the regular json.Unmarshal function to get your values, for example:

	var p Payload
	_ = json.Unmarshal([]byte(`{
        	&quot;somefield1&quot;:&quot;somevalue1&quot;,
        	&quot;somefield2&quot;:null
        }`), &amp;p)
	fmt.Printf(&quot;Should be defined == true and value == nil: \n%+v\n\n&quot;, p)


	p = Payload{}
	_ = json.Unmarshal([]byte(`{&quot;somefield1&quot;:&quot;somevalue1&quot;}`), &amp;p)
	fmt.Printf(&quot;Should be defined == false \n%+v\n\n&quot;, p)
	
	p = Payload{}
	_ = json.Unmarshal([]byte(`{
        	&quot;somefield1&quot;:&quot;somevalue1&quot;,
        	&quot;somefield2&quot;:&quot;somevalue2&quot;
        }`), &amp;p)
	fmt.Printf(&quot;Parsed should be defined == true and value != nil \n%+v\n&quot;, p)
	if p.SomeField2.Value != nil {
	  fmt.Printf(&quot;SomeField2&#39;s value is %s&quot;, *p.SomeField2.Value)
	}

Should give you this output:

Should be defined == true and value == nil: 
{SomeField1:somevalue1 SomeField2:{Defined:true Value:&lt;nil&gt;}}

Should be defined == false 
{SomeField1:somevalue1 SomeField2:{Defined:false Value:&lt;nil&gt;}}

Parsed should be defined == true and value != nil 
{SomeField1:somevalue1 SomeField2:{Defined:true Value:0xc000010370}}
SomeField2&#39;s value is somevalue2

Link to the playground with the full example: https://play.golang.org/p/AUDwPKHBs62

Do note that you will need one struct for each type you want to wrap, so, if you need an optional number you'll need to create an OptionalFloat64 (all JSON numbers can be 64 bit floats) struct with a similar implementation.
If/when generics land in Go, this could be simplified to a single generic struct.

答案3

得分: 7

好的,以下是翻译好的内容:

好问题。

我认为你可以使用https://golang.org/pkg/encoding/json/#RawMessage,代码如下:

type MyMessage struct {
  somefield1 string
  somefield2 json.RawMessage
}

因此,在解组后,如果是null,你应该得到[]byte("null"),如果缺失,则得到nil

这是一个示例代码:https://play.golang.org/p/UW8L68K068

英文:

Good question.

I believe you can use https://golang.org/pkg/encoding/json/#RawMessage as:

type MyMessage struct {
  somefield1 string
  somefield2 json.RawMessage
}

So after unmarshalling you should have []byte(&quot;null&quot;) in case of null and nil if missing.

Here is a playground code: https://play.golang.org/p/UW8L68K068

答案4

得分: 5

如果你将对象反序列化为map[string]interface{},那么你可以通过检查字段是否存在来判断。

type unMarshalledObject map[string]interface{}
json.Unmarshal(input, &unMarshalledObject{})
_, ok := unMarshalledObject["somefield2"]

Go Playground

英文:

If you unmarshall the object into a map[string]interface{} then you can just check if a field is there

type unMarshalledObject map[string]interface{}
json.Unmarshal(input, unMarshalledObject)
_, ok := unMarshalledObject[&quot;somefield2&quot;]

Go Playground

答案5

得分: 0

如果结构字段是指针,JSON解码器将在字段存在时分配新变量,如果字段不存在,则将其保留为nil。因此,我建议使用指针。

type Data struct {
    StrField *string
    IntField *int
}
...
if data.StrField != nil {
    handle(*data.StrField)
}
英文:

If struct field is a pointer, JSON decoder will allocate new variable if the field is present or leave it nil if not. So I suggest to use pointers.

type Data struct {
    StrField *string
    IntField *int
}
...
if data.StrField != nil {
    handle(*data.StrField)
}

答案6

得分: 0

使用github.com/golang/protobuf/ptypes/struct和jsonpb github.com/golang/protobuf/jsonpb,你可以这样做:

func TestFunTest(t *testing.T) {
    p := &pb.KnownTypes{}
    e := UnmarshalString(`{"val":null}`, p)
    fmt.Println(e, p)
    p = &pb.KnownTypes{}
    e = UnmarshalString(`{"val":1}`, p)
    fmt.Println(e, p)
    p = &pb.KnownTypes{}
    e = UnmarshalString(`{"val":"string"}`, p)
    fmt.Println(e, p)
    p = &pb.KnownTypes{}
    e = UnmarshalString(`{}`, p)
    fmt.Println(e, p)
}

输出结果:

[ `go test -test.run="^TestFunTest$"` | done: 1.275431416s ]
    <nil> val:<null_value:NULL_VALUE > 
    <nil> val:<number_value:1 > 
    <nil> val:<string_value:"string" > 
    <nil> 
    PASS
英文:

By using github.com/golang/protobuf/ptypes/struct and jsonpb github.com/golang/protobuf/jsonpb, you can do like this:

func TestFunTest(t *testing.T) {
	p := &amp;pb.KnownTypes{}
	e := UnmarshalString(`{&quot;val&quot;:null}`, p)
	fmt.Println(e, p)
	p = &amp;pb.KnownTypes{}
	e = UnmarshalString(`{&quot;val&quot;:1}`, p)
	fmt.Println(e, p)
	p = &amp;pb.KnownTypes{}
	e = UnmarshalString(`{&quot;val&quot;:&quot;string&quot;}`, p)
	fmt.Println(e, p)
	p = &amp;pb.KnownTypes{}
	e = UnmarshalString(`{}`, p)
	fmt.Println(e, p)
}

Output:

[ `go test -test.run=&quot;^TestFunTest$&quot;` | done: 1.275431416s ]
	&lt;nil&gt; val:&lt;null_value:NULL_VALUE &gt; 
	&lt;nil&gt; val:&lt;number_value:1 &gt; 
	&lt;nil&gt; val:&lt;string_value:&quot;string&quot; &gt; 
	&lt;nil&gt; 
	PASS

huangapple
  • 本文由 发表于 2016年4月13日 22:22:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/36601367.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定