JSON unmarshal in GoLang

  sonic0002        2019-11-09 21:57:30       12,901        0    

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

JSON  GOLANG  UNMARSHAL  EMPTY INTERFACE 

       

  RELATED


  0 COMMENT


No comment for this article.



  RANDOM FUN

How much does a good telescope cost from Google