JW.
JW.

Reputation: 2471

How to specify default values when parsing JSON in Go

I want to parse a JSON object in Go, but want to specify default values for fields that are not given. For example, I have the struct type:

type Test struct {
    A string
    B string
    C string
}

The default values for A, B, and C, are "a", "b", and "c" respectively. This means that when I parse the json:

{"A": "1", "C": 3}

I want to get the struct:

Test{A: "1", B: "b", C: "3"}

Is this possible using the built-in package encoding/json? Otherwise, is there any Go library that has this functionality?

Upvotes: 46

Views: 64273

Answers (4)

Elviss Strazdins
Elviss Strazdins

Reputation: 1484

You can also implement the UnmarshalJSON interface and set the default values in it.

func (test *Test) UnmarshalJSON(data []byte) error {
    test.A = "1"
    test.B = "2"
    test.C = "3"

    type tempTest Test
    return json.Unmarshal(data, (*tempTest)(test))
}

The type tempTest is needed so that UnmarshalJSON is not called recursively.

Upvotes: 5

zangw
zangw

Reputation: 48536

The json.Unmarshal with a default value is simple and clean like the answers given by Christian and JW., but it has some downsides.

  • First, it strongly ties the default values of fields with the parsing logic. It's conceivable that we want to let user code down the line set its defaults; right now, the defaults have to be set before unmarshaling.
  • The second downside is that it only works in simple cases. If our Options struct has a slice or map of other structs, we can't populate defaults this way.

Another option is Default values with pointer fields

    type Test struct {
        A *string
        B *string
        C *string
    }

    js := []byte(`{"A": "1", "C": "3"}`)

    var t Test
    if err := json.Unmarshal(js, &t); err != nil {
        fmt.Println(err)
    }

    if t.B == nil {
        var defaultB = "B"
        t.B = &defaultB
    }

Upvotes: 2

JW.
JW.

Reputation: 2471

This is possible using encoding/json: when calling json.Unmarshal, you do not need to give it an empty struct, you can give it one with default values.

For your example:

var example []byte = []byte(`{"A": "1", "C": "3"}`)

out := Test{
    A: "default a",
    B: "default b",
    // default for C will be "", the empty value for a string
}
err := json.Unmarshal(example, &out) // <--
if err != nil {
    panic(err)
}
fmt.Printf("%+v", out)

Running this example returns {A:1 B:default b C:3}.

As you can see, json.Unmarshal(example, &out) unmarshals the JSON into out, overwriting the values specified in the JSON, but leaving the other fields unchanged.

Upvotes: 100

Christian
Christian

Reputation: 3721

In case u have a list or map of Test structs the accepted answer is not possible anymore but it can easily be extended with a UnmarshalJSON method:

func (t *Test) UnmarshalJSON(data []byte) error {
  type testAlias Test
  test := &testAlias{
    B: "default B",
  }

  err := json.Unmarshal(data, test)
  if err != nil {
      return err
  }

  *t = Test(*test)
  return nil
}

The testAlias is needed to prevent recursive calls to UnmarshalJSON. This works because a new type has no methods defined.

https://play.golang.org/p/qiGyjRbNHg

Upvotes: 32

Related Questions