Javascript Object Notation known as JSON, is the universally accepted format to exchange data. It is the lightweight for storing and transferring the data across devices or medium. Stating the definition as json.org says, JSON is a text format that is completely language independent but uses conventions that are familiar to programmers of the C-family of languages, including C, C++, C#, Java, JavaScript, Perl, Python, and many others. These properties make JSON an ideal data-interchange language.

The sample JSON structure for profile information can be written as,

{ "name": "Sibi", "age": 27, "nationality": "Indian" }

All languages uses certain packages and ways to convert their native data types to the JSON standard before sharing the information. In this article I will explain how the data in Go programming language gets converted and mapped to its native data type.

Let’s take the example mentioned above which display the profile information of a user in this case it’s mine. If I have backend service running in go and mobile or web app requests for the profile information, frontend will receive the data in JSON format served over HTTPS protocol. But before sending over that information, let’s see how that will be processed over in our backend platform.

The strut definition for the above example will be,

type Profile struct {
    Name        string
    Age         int
    Nationality string
}

we have three fields, two of which accepts string type and one integer field. Now we need to use encoding/json package which is available as an in-built package for golang.

It provides two main functionalities, (i) Marshal - encoding the native type to JSON format (while sending the information externally) (ii) Unmarshal - decoding the received JSON to native type

JSON encode (Marshal)

To encode JSON data of the Profile struct we need to create an instance by,

u := Profile{Name: "Sibi", Age: 27, Nationality: "Indian"}

we can Marshal the instance to JSON by,

c, err := json.Marshal(u)

If it process without any error, it will return the byte array as response

[]byte{`Name: "Sibi", Age: 27, Nationality: "Indian"`}

If you notice the key name in the JSON follows similar to the variable declaration. Since JSON does not have any rule over its naming conventions but it can be used by language of any choice so usually all the names will be kept to smaller case to avoid the confusion nad maintain the standard throughout the application.

In order to rule over this, we can control this at the struct declaration by denoting its corresponding JSON key name like,

type Profile struct {
    Name        string `json:"name"`
    Age         int    `json:"age"`
    Nationality string `json:"nationality"`
}

following this way we can change the key name for the JSON field without affecting the variable reference in the future.

To construct the JSON with the object of object structure we need to refer the struct field to an another struct variable. For example adding the address field to it will take few properties so it can be constructed as,

type Profile struct {
    Name        string  `json:"name"`
    Age         int     `json:"age"`
    Nationality string  `json:"nationality"`
    Address     Address `json:"address"`
}

type Address struct {
    City  string `json:"city"`
    State string `json:"state"`
}

and the instance creation also needs the similar update as,

u := Profile{Name: "Sibi", Age: 27, Nationality: "Indian", Address: Address{City: "Bangalore", State: "Karnataka"}}

JSON decode (Unmarshal) The JSON decode follows similar to the Marshal process to encode. For example,

jsonstring := `{Name:Sibi Age:27 Nationality:Indian}`
var profiledata Profile
err := json.Unmarshal([]byte(jsonstring), &profiledata)

The JSON marshall accepts two params the byte string input and the variable of the type with it is being decoded to. This will work even if we have object of object structure in our input json data. The main struct needs to be in the proper format in order to decode without any error.

One common issue when working with third party data is that we cannot assure the format or data can be changed over a period of time. We have interface to tackle this issue. The profiledata variable will be decalared as,

var profiledata interface{}

To access the type of value that’s underlying each key we can,

data := profiledata.(map[string]interface{})

for keyname, v := range data {
    switch datatype := v.(type) {
    case string:
        fmt.Println(keyname, "is string", datatype)
    case float64:
        fmt.Println(keyname, "is float64", datatype)
    case []interface{}:
        fmt.Println(keyname, "is an array:")
        for i, u := range datatype {
            fmt.Println(i, u)
        }
    default:
        fmt.Println(keyname, "is of a type I don't know how to handle")
    }
}

The complete explanations mentioned in this blog is compiled into a single program written below,

package main

import (
    "encoding/json"
    "fmt"
)

type Profile struct {
    Name        string  `json:"name"`
    Age         int     `json:"age"`
    Nationality string  `json:"nationality"`
    Address     Address `json:"address"`
}

type Address struct {
    City  string
    State string
}

func main() {
    userProfile := Profile{Name: "Sibi", Age: 27, Nationality: "Indian", Address: Address{City: "Bangalore", State: "Karnataka"}}
    fmt.Printf("%+v\n", userProfile)

    byteArray, err := json.Marshal(userProfile)
    if err != nil {
        fmt.Println(err)
    }

    fmt.Println(string(byteArray))

    jsondata := []byte(`{"Name":"Sibi","Age":27,"Nationality":"Indian","Address":{"City":"Bangalore","State":"Karnataka"}}`)
    var f map[string]interface{}
    err = json.Unmarshal(jsondata, &f)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println("The decoded json data : ", f)
}