golang: Encode/Decode arbitrary JSON

Just a quick post in case I forget how to do this in future. I had a use case for working with reading arbitrary JSON from a file and parsing it using Golang, which meant that the recommended way of using structs wasn’t suitable.

json.Marshal

The first thing to do was marshal a custom data structure into being a JSON string using json.Marshal. This was fairly straightforward to do:

cacheContent := map[string]interface{}{
    "foo": "bar",
    "baz": map[string]interface{}{
        "bee": "boo",
    },
}

str, err := json.Marshal(cacheContent)
if err != nil {
    fmt.Println("Error encoding JSON")
    return
}

fmt.Println(string(str))
# => {"baz":{"bee":"boo"},"foo":"bar"}

json.Unmarshal

json.Unmarshal was the tough one to work out. As it turns out, all you need to do is define a map of string => interface and then unmarshal into this structure. Go will take care of the rest.

# Using the "str" variable that we created using json.Marshal earlier
var x map[string]interface{}
json.Unmarshal([]byte(str), &x)
fmt.Println("%v", x)
# => %v map[baz:map[bee:boo] foo:bar]

# Using some hand crafted JSON. This could come from a file, web service, anything
str2 := "{"foo":{"baz": [1,2,3]}}"

var y map[string]interface{}
json.Unmarshal([]byte(str2), &y)

fmt.Println("%v", y)
# => %v map[foo:map[baz:[1 2 3]]]

Once the data is un-marshalled, you can access it as follows:

fmt.Printf("%v", y["foo"].(map[string]interface{})["baz"])
# => [1,2,3]

As we’re un-marshalling into an interface, we need to inform go what data type each key is before we can perform operations on it. This is what the .(map[string]interface{}) does

Michael is a polyglot software engineer, committed to reducing complexity in systems and making them more predictable. Working with a variety of languages and tools, he shares his technical expertise to audiences all around the world at user groups and conferences. You can follow @mheap on Twitter

Thoughts on this post

PeterJ 2015-08-18

Thought you might be interested in adding use of reflection to work through more complex JSON where maps and lists contain arbitrary (JSON) types, Sample code below:


package main

import (
    "fmt"
    "encoding/json"
    "reflect"
)

func main() {
//ref:  http://michaelheap.com/golang-encodedecode-arbitrary-json/  18/8/15

    // Using some hand crafted JSON. This could come from a file, web service, anything
    str2 := "{\"foo\":{\"baz\": [1,2,3]}, \"flag\":true, \"list\":[\"one\", 2, true, \"4\", {\"key\":\"value\"}, [1, true]]}"

    var y map[string]interface{}
    json.Unmarshal([]byte(str2), &y)

    fmt.Printf("%+v\n", y)
    //# => map[foo:map[baz:[1 2 3]] flag:true list:[one 2 true 4 map[key:value] [1 true]]]

/*
As we’re un-marshalling into an interface, we need to inform go what data type
each key is before we can perform operations on it. Go provides a the "reflect"
package which we can use to process arbitrarily complex data structures:
*/

    the_list := y["list"].([]interface{})
    for n, v := range the_list {
        fmt.Printf("index:%d  value:%v  kind:%s  type:%s\n", n, v, reflect.TypeOf(v).Kind(), reflect.TypeOf(v))
    }

    //# =>
    //index:0  value:one  kind:string  type:string
    //index:1  value:2  kind:float64  type:float64
    //index:2  value:true  kind:bool  type:bool
    //index:3  value:4  kind:string  type:string
    //index:4  value:map[key:value]  kind:map  type:map[string]interface {}
    //index:5  value:[1 true]  kind:slice  type:[]interface {}
}
yolo 2017-12-06

I use a type alias for interface{} for climbing through more complex json objects:

type I = interface{}

json[“obj”].(I)[“foo”].(I)[“bar”]

Leave a comment?

Leave a Reply