JSON解组中缺失字段和空字段之间的区别

JSON解组中缺失字段和空字段之间的区别

问题描述:

So I have this struct in Go:

type Car struct {
    Name  string `json:"name"`
    Speed int    `json:"speed"`
}

And I have two JSON samples that I unmarshal:

str := `{"name": "", "speed": 0}`
strTwo := `{}`

I do the unmarshaling in this way:

car := Car{}
_ = json.Unmarshal([]byte(str), &car)

carTwo := Car{}
_ = json.Unmarshal([]byte(strTwo), &carTwo)

Now because of the way Go deals with default value types, when I try to print the structure, I get the same result:

car - { 0}
carTwo - { 0}

So I can't see the difference between a missing value in JSON and when a default value is passed. How can I solve this problem?

One way is to use pointers in struct:

type Car struct {
    Name  *string `json:"name"`
    Speed *int    `json:"speed"`
}

But I get a very ugly code when using this values, I have to do pointer dereferencing everywhere

Go's primitive data types are not suitable to handle the "all valid values" and an additional "is present" information.

If you do need this, one way is to use pointers, where the nil pointer value corresponds to the "missing" state.

If it is uncomfortable to work with pointers afterwards, do a "post processing": convert your structs with pointer fields to a struct value with non-pointer fields, so you can work with that later on.

You may do this "manually", or write a custom unmarshaler to make this happen automatically.

Here's an example how to do it:

type PCar struct {
    Name  *string `json:"name"`
    Speed *int    `json:"speed"`
}

type Car struct {
    Name  string `json:"-"`
    Speed int    `json:"-"`
    PCar
}

func (c *Car) UnmarshalJSON(data []byte) error {
    if err := json.Unmarshal(data, &c.PCar); err != nil {
        return err
    }

    if c.PCar.Name != nil {
        c.Name = *c.PCar.Name
    }
    if c.PCar.Speed != nil {
        c.Speed = *c.PCar.Speed
    }
    return nil
}

Example using it:

sources := []string{
    `{"name": "", "speed": 0}`,
    `{}`,
    `{"name": "Bob", "speed": 21}`,
}

for i, src := range sources {
    var c Car
    if err := json.Unmarshal([]byte(src), &c); err != nil {
        panic(err)
    }
    fmt.Println("car", i, c)
}

Output (try it on the Go Playground):

car 0 { 0 {0x40c200 0x4140ac}}
car 1 { 0 {<nil> <nil>}}
car 2 {Bob 21 {0x40c218 0x41410c}}

As you can see, car 1 contains 2 non-nil pointers, because the respective fields were present in the input JSON, while car 2 contains 2 nil pointers, because those fields were missing in its input. You may use Car.Name and Car.Speed fields as non-pointers (because they are not pointers). To tell if they were present in the input, you may check the corresponding pointers Car.PCar.Name and Car.PCar.Speed if they are nil.