7

I would like to iterate over the fields of a struct after unmarshalling a JSON object into it and check for the fields whose value was not set (i.e. are empty).

I can get the value of each field and compare that to the reflect.Zero value for the corresponding type

json.Unmarshal([]byte(str), &res)
s := reflect.ValueOf(&res).Elem()
typeOfT := s.Type()
for i := 0; i < s.NumField(); i++ {
    f := s.Field(i)
    v := reflect.ValueOf(f.Interface())
    if (reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface())) {
    ....

But the problem, of course, is that this will not work well for bool or int values. If a bool field is set to false in the JSON or an int field is set to 0, they will be equal to the zero value of their type. The aforementioned check will consider the fields to be uninitialized, even though they actually have a value set.

I know one way around this is to use pointers, but I just don't see how that would be possible in this case as I'm working with reflect.Value types, not the actual struct.

6
  • 2
    If it's an object you can unmarshall it to the map[string]interface{} and check if the key is in the map. Commented May 30, 2018 at 6:34
  • I would like to be able to use the same method to test for empty fields on nested structs within the res struct so unfortunately I don't think this will work. Commented May 30, 2018 at 6:58
  • 1
    Well, you simply cannot do this. Don't try, you can't. You have to redesign: Either use e.g. *bool and check for nil or use map[string]interface{} and check for existence of the key. BTW: Duplicate. Commented May 30, 2018 at 7:25
  • @user2969402 there is nothing that forbids you to use the reflect package to check if a pointer field is nil or not, so if you use pointers you should be fine. Commented May 30, 2018 at 7:30
  • 1
    What is your end goal? Why do you want to do this? There's probably a better approach. Commented May 30, 2018 at 8:51

3 Answers 3

8

As you've mentioned, you could use pointers.

The json package can handle unmarshalling values into pointers for you. You've not included the json payload you are trying to unmarshal, or the struct you are unmarshalling into, so I've made up an example.

// json
{
    "foo": true,
    "number_of_foos": 14
}

// go struct
type Foo struct {
    Present bool `json:"foo"`
    Num     int  `json:"number_of_foos"`
}

Here if the keys foo or number_of_foos is missing, then as you've correctly observed, the zero value (false/ 0) will be used. In general the best advice is to make use of the zero value. Create structures so that zero values of false are useful, rather than a pain. This is not always possible, so changing the types of the fields in the Foo struct to be pointers will allow you to check the 3 cases you are after.

  1. Present
  2. Present and zero
  3. Missing

here is the same struct with pointers:

// go struct
type Foo struct {
    Present *bool `json:"foo"`
    Num     *int  `json:"number_of_foos"`
}

Now you can check for presence of the value with fooStruct.Present != nil and if that condition holds, you can assume that the value in the field is the one you wanted.

There is no need to use the reflect package.

Sign up to request clarification or add additional context in comments.

Comments

3

Another way of doing the same is by implementing json.Unmarshaler.

type MaybeInt struct {
    Present bool
    Value   int
}

func (i *MaybeInt) UnmarshalJSON(bs []byte) error {
    if e := json.Unmarshal(bs, &i.Value); e != nil {
        return e
    }
    i.Present = true
    return nil
}

You can then use MaybeInt in your top-level structure:

type Top struct {
    N MaybeInt `json:"n"`
    M MaybeInt `json:"m"`
}

func main() {
    t := Top{}
    if e := json.Unmarshal([]byte(` { "n": 4930 } `), &t); e != nil {
        panic(e)
    }
    fmt.Println(t.N, t.M)
}

See it working on the playground

Comments

0

Try using the golang validator package. The validator package offers a required attribute that might do the required job for your need. The official documentation for required attribute states:

This validates that the value is not the data types default zero value. For numbers ensures value is not zero. For strings ensures value is not "". For slices, maps, pointers, interfaces, channels and functions ensures the value is not nil.

The example illustrating the same can be seen at: https://github.com/go-playground/validator/blob/v9/_examples/struct-level/main.go.

Hope this solves your requirement.

1 Comment

This depends what OP wants. If he accepts that zero value is a valid value coming from the unmarshaling string, then it still presents the same issue. When we use it, we can't tell if the struct was initialized with integer 0 passed in, or it was completely missing in marshaled string missing, therefore invalid and our struct just treats it as optional, because 0 is default integer value regardless.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.