Share some tips on using JSON in Golang

  • 2020-06-03 06:55:26
  • OfStack

preface

Sometimes fields passed upstream are of type string, but we want to use Numbers instead. One json:",string" will suffice, but if you don't know these golang tricks, you'll have a hard time.

Reference: JSON and struct composition in Go

Temporarily ignore the struct field


type User struct {
 Email string `json:"email"`
 Password string `json:"password"`
 // many more fields ... 
}

Temporarily ignore the Password field


json.Marshal(struct {
 *User
 Password bool `json:"password,omitempty"`
}{
 User: user,
})

Temporarily add additional fields


type User struct {
 Email string `json:"email"`
 Password string `json:"password"`
 // many more fields ... 
}

Temporarily ignore the Password field and add the token field


json.Marshal(struct {
 *User
 Token string `json:"token"`
 Password bool `json:"password,omitempty"`
}{
 User: user,
 Token: token,
})

Temporary bonding of two struct


type BlogPost struct {
 URL string `json:"url"`
 Title string `json:"title"`
}

type Analytics struct {
 Visitors int `json:"visitors"`
 PageViews int `json:"page_views"`
}

json.Marshal(struct{
 *BlogPost
 *Analytics
}{post, analytics})

One json was cut into two struct


json.Unmarshal([]byte(`{
 "url": "attila@attilaolah.eu",
 "title": "Attila's Blog",
 "visitors": 6,
 "page_views": 14
}`), &struct {
 *BlogPost
 *Analytics
}{&post, &analytics})

Temporarily rename the field struct


type CacheItem struct {
 Key string `json:"key"`
 MaxAge int `json:"cacheAge"`
 Value Value `json:"cacheValue"`
}

json.Marshal(struct{
 *CacheItem

 // Omit bad keys
 OmitMaxAge omit `json:"cacheAge,omitempty"`
 OmitValue omit `json:"cacheValue,omitempty"`

 // Add nice keys
 MaxAge int `json:"max_age"`
 Value *Value `json:"value"`
}{
 CacheItem: item,

 // Set the int by value:
 MaxAge: item.MaxAge,

 // Set the nested struct by reference, avoid making a copy:
 Value: &item.Value,
})

Pass a number as a string


type TestObject struct {
 Field1 int `json:",string"`
}

The corresponding json is {"Field1": "100"}

An error is reported if json is {"Field1": 100}

Tolerates string and number conversions

If you are using jsoniter, you can enable fuzzy mode to support JSON as passed by PHP.


import "github.com/json-iterator/go/extra"

extra.RegisterFuzzyDecoders()

This allows you to handle the problem of incorrect string and number types. Such as


var val string
jsoniter.UnmarshalFromString(`100`, &val)

And such as


json.Marshal(struct {
 *User
 Password bool `json:"password,omitempty"`
}{
 User: user,
})
0

Tolerates empty arrays as objects

Another frustrating aspect of PHP is that if PHP array is empty, the serialization is []. But when it's not empty, it's serialized {"key":"value"} . We need to treat [] as a {}.

If you are using jsoniter, you can enable fuzzy mode to support the JSON passed by FROM PHP.


json.Marshal(struct {
 *User
 Password bool `json:"password,omitempty"`
}{
 User: user,
})
1

This will support it


json.Marshal(struct {
 *User
 Password bool `json:"password,omitempty"`
}{
 User: user,
})
2

time. Time is supported with MarshalJSON

golang will put by default time.Time Serialize as a string. If we want to do it the other way time.Time , you need to customize the type and define MarshalJSON.


type timeImplementedMarshaler time.Time

func (obj timeImplementedMarshaler) MarshalJSON() ([]byte, error) {
 seconds := time.Time(obj).Unix()
 return []byte(strconv.FormatInt(seconds, 10)), nil
}

MarshalJSON is called when serialized


json.Marshal(struct {
 *User
 Password bool `json:"password,omitempty"`
}{
 User: user,
})
4

RegisterTypeEncoder is used to support ES114en.Time

jsoniter is able to customize JSON codec for type that is not defined by you. Such as for time.Time You can serialize with epoch int64


json.Marshal(struct {
 *User
 Password bool `json:"password,omitempty"`
}{
 User: user,
})
5

See the implementation code for RegisterTimeAsInt64Codec for customization

Using MarshalText supports non-string as map for key

Although the JSON standard only supports string as map of key. But golang passed MarshalText() Interface so that other types can also be key for map. For example,


json.Marshal(struct {
 *User
 Password bool `json:"password,omitempty"`
}{
 User: user,
})
6

big.Float realized MarshalText().

Using json RawMessage

If part of the json document does not have a standard format, we can save the original text information in string.


json.Marshal(struct {
 *User
 Password bool `json:"password,omitempty"`
}{
 User: user,
})
7

Using json Number

By default, if yes interface{} The case for the corresponding number would be of type float64. If you enter a large number, this representation will compromise the accuracy. So you can UseNumber() To enable the json.Number To represent Numbers as strings.


decoder1 := json.NewDecoder(bytes.NewBufferString(`123`))
decoder1.UseNumber()
var obj1 interface{}
decoder1.Decode(&obj1)
should.Equal(json.Number("123"), obj1)

jsoniter supports this use of the standard library. At the same time, extended behavior allows Unmarshal to support UseNumber.


json.Marshal(struct {
 *User
 Password bool `json:"password,omitempty"`
}{
 User: user,
})
9

Change the naming style of the field

Often the field name in JSON is different from the field name in Go. We can modify it with field tag.


output, err := jsoniter.Marshal(struct {
 UserName string `json:"user_name"`
 FirstLanguage string `json:"first_language"`
}{
 UserName: "taowen",
 FirstLanguage: "Chinese",
})
should.Equal(`{"user_name":"taowen","first_language":"Chinese"}`, string(output))

But 1 each field to set, too much trouble. If jsoniter is used, we can set the naming style with 1.


import "github.com/json-iterator/go/extra"

extra.SetNamingStrategy(LowerCaseWithUnderscores)
output, err := jsoniter.Marshal(struct {
 UserName string
 FirstLanguage string
}{
 UserName: "taowen",
 FirstLanguage: "Chinese",
})
should.Nil(err)
should.Equal(`{"user_name":"taowen","first_language":"Chinese"}`, string(output))

Use private fields

Go's standard library only supports field of public. jsoniter has additional support for private's field. You need to use SupportPrivateFields() To turn on the switch.


import "github.com/json-iterator/go/extra"

extra.SupportPrivateFields()
type TestObject struct {
 field1 string
}
obj := TestObject{}
jsoniter.UnmarshalFromString(`{"field1":"Hello"}`, &obj)
should.Equal("Hello", obj.field1)

conclusion


Related articles: