In almost all mainstream programming languages, there is either built-in or third-party library to support parse JSON data because it is so popular for organizing and transferring data among different services. In GoLang, the built in json package also provides functions for marshalling and unmarshalling JSON data. Marshalling GoLang struct to a JSON string is relatively simple, just need to call json.Marshal(), it's a bit complicated to unmarshal an arbitrary JSON string into a GoLang struct object though. This post will try to walk through some scenarios of unmarshalling JSON string to GoLang struct object.
It's pretty straight forward to unmarshal some simple JSON string with simple structures where the values are just simple primitive values like boolean, number, string etc.
package main
import (
"fmt"
"encoding/json"
"time"
)
func main() {
type FruitBasket struct {
Name string
Fruit []string
Id int64 `json:"ref"`// 声明对应的json key
Created time.Time
}
jsonData := []byte(`
{
"Name": "Standard",
"Fruit": [
"Apple",
"Banana",
"Orange"
],
"ref": 999,
"Created": "2018-04-09T23:00:00Z"
}`)
var basket FruitBasket
err := json.Unmarshal(jsonData, &basket)
if err != nil {
fmt.Println(err)
}
fmt.Println(basket.Name, basket.Fruit, basket.Id)
fmt.Println(basket.Created)
}
Just convert the JSON string into a byte slice because json.Unmarshal() takes a byte slice. And then call json.Unmarshal() to unmarshal it into a predefined struct with similar property definitions and structures.
What if there is a nested JSON object in one of it's key value?
jsonData := []byte(`
{
"Name": "Standard",
"Fruit" : {"Name": "Apple", "PriceTag": "$1"},
"ref": 999,
"Created": "2018-04-09T23:00:00Z"
}`)
It's still not that complicated as we just need to define a similar GoLang struct.
type Fruit struct {
Name string `json":Name"`
PriceTag string `json:"PriceTag"`
}
type FruitBasket struct {
Name string
Fruit Fruit
Id int64 `json:"ref"`// 声明对应的json key
Created time.Time
}
It's similar if there is an embedded array in one of the value of the JSON object.
"Fruit" : [
{
"Name": "Apple",
"PriceTag": "$1"
},
{
"Name": "Pear",
"PriceTag": "$1.5"
}
]
We just need to define a struct and also declare the variable as a slice.
type Fruit struct {
Name string `json:"Name"`
PriceTag string `json:"PriceTag"`
}
type FruitBasket struct {
Name string
Fruit []Fruit
Id int64 `json:"ref"`// the corresponding JSON key
Created time.Time
}
Now it's a bit tricky to unmarshal a JSON object with dynamic key.
"Fruit" : {
"1": {
"Name": "Apple",
"PriceTag": "$1"
},
"2": {
"Name": "Pear",
"PriceTag": "$1.5"
}
}
The Fruit object is an object of objects. It's not like an array of objects anymore and its keys are dynamic. How are we going to define the GoLang struct to handle this? The answer is we define a Fruit struct and then define a wrapper struct which contains a map of Fruits where the map key is the dynamic key in JSON object and value is a Fruit object.
type Fruit struct {
Name string `json:"Name"`
PriceTag string `json:"PriceTag"`
}
type FruitBasket struct {
Name string
Fruit map[string]Fruit
}
An example code snippet.
package main
import (
"fmt"
"encoding/json"
"time"
)
func main() {
type Fruit struct {
Name string `json:"Name"`
PriceTag string `json:"PriceTag"`
}
type FruitBasket struct {
Name string
Fruit map[string]Fruit
Id int64 `json:"ref"`// corresponding JSON key
Created time.Time
}
jsonData := []byte(`
{
"Name": "Standard",
"Fruit" : {
"1": {
"Name": "Apple",
"PriceTag": "$1"
},
"2": {
"Name": "Pear",
"PriceTag": "$1.5"
}
},
"ref": 999,
"Created": "2018-04-09T23:00:00Z"
}`)
var basket FruitBasket
err := json.Unmarshal(jsonData, &basket)
if err != nil {
fmt.Println(err)
}
for _, item := range basket.Fruit {
fmt.Println(item.Name, item.PriceTag)
}
}
Finally comes to the big boss where we have arbitrary arrays and objects in a JSON object. How are we going to do now? For arbitrary JSON data, encoding/json
package also allows to use empty interface to take any data with unknown structure.
- map[string]interface{} stores JSON object
- []interface stores JSON array
json.Unmarshal would put any valid JSON data into an interface{} and later do a type conversion when need to use it.
jsonData := []byte(`{"Name":"Eve","Age":6,"Parents":["Alice","Bob"]}`)
var v interface{}
json.Unmarshal(jsonData, &v)
data := v.(map[string]interface{})
for k, v := range data {
switch v := v.(type) {
case string:
fmt.Println(k, v, "(string)")
case float64:
fmt.Println(k, v, "(float64)")
case []interface{}:
fmt.Println(k, "(array):")
for i, u := range v {
fmt.Println(" ", i, u)
}
default:
fmt.Println(k, v, "(unknown)")
}
}
Although it's possible to unmarshal any valid JSON data into interface{}, there still might be some unpredicted behavior where a number string might be parsed into a float number which would cause trouble in some cases. Hence please stick to define the struct with type explicitly if you know the concrete structure of the JSON data you want to unmarshal.
Reference: https://segmentfault.com/a/1190000019787975