Sankalp Mishra Sankalp Mishra - 2 months ago 7
JSON Question

How to parse nested JSON into structs in Go?

How can I map a nested JSON to a nested Struct like this:

type ChildStruct1 struct {
key3,
key4,string
}
type ChildStruct2 struct {
key4,
key5,string
}

type MainStruct struct {
key1,
key2,string
childStruct1 ChildStruct1
childStruct2 ChildStruct1
}


Input JSON:

{
key1: val1,
key2: val2,
chilDStruct1 : {
key3: val3,
key4: val4,
}
chilDStruct2 : {
key5: val5,
key6: val6,
}
}


How can this be mapped. I have used jsonMapper in Java but not sure how to do it here. I tried this but this is not working:

var data MainStruct
file, err := ioutil.ReadFile("jsonData.json")
if err != nil {
log.Fatal(err)
}
err = json.Unmarshal(file, &data)
if err != nil {
log.Fatal(err)
}
fmt.Println(data.key1)


I tried this also:

u := &MainStruct{}
file, err := ioutil.ReadFile("jsonData.json")
if err != nil {
log.Fatal(err)
}
err = json.Unmarshal([]byte(file), u)
if err != nil {
log.Fatal(err)
}
fmt.Println(u.key1)


But output is always
0
.

Answer

You have quite some errors in your example (both in JSON text and source code). Let's fix them.

JSON text

First your JSON is not valid. Try this instead of yours:

{
    "key1": "val1",
    "key2": "val2",
    "childStruct1" : {
        "key3": "val3",
        "key4": "val4"
    },
    "childStruct2" : {
        "key5": "val5",
        "key6": "val6"
    }
}

Go type definitions

Second, your Go source defining your struct types has syntax errors.

Third, you have to export struct fields in order so the json package can set their values. Try this instead of yours:

type ChildStruct1 struct {
    Key3 string
    Key4 string
}

type ChildStruct2 struct {
    Key4 string
    Key5 string
}

type MainStruct struct {
    Key1         string
    Key2         string
    ChildStruct1 ChildStruct1
    ChildStruct2 ChildStruct2
}

Code to parse the JSON text

And now on to the code to parse the JSON text. You can read the whole file into memory using ioutil.ReadFile(), but often it is more efficient to not read the whole file but to "stream" it to a decoder (using the file as the input).

os.Open() returns a *File. It is not a []byte, but it implements io.Reader. So you can't pass it to json.Unmarshal(). Instead create a json.Decoder using json.NewDecoder() which accepts an io.Reader, so you can pass the opened file. And don't forget to close the opened file using File.Close(), best to close it as a deferred statement (if opening it succeeds).

f, err := os.Open("jsonData.json")
if err != nil {
    log.Panic(err)
}
defer f.Close()

var data MainStruct
err = json.NewDecoder(f).Decode(&data)
if err != nil {
    log.Panic(err)
}
fmt.Println(data.Key1)
fmt.Printf("%+v", data)

(Note that I used log.Panic() instead of log.Fatal() as the latter calls os.Exit() and hence our deferred function would not be called.)

Output:

val1
{Key1:val1 Key2:val2 ChildStruct1:{Key3:val3 Key4:val4} ChildStruct2:{Key4: Key5:val5}}

Modified version for the Go Playground

Here is a modified version that parses the JSON text from a constant defined in the source code, try it on the Go Playground:

var data MainStruct
err := json.Unmarshal([]byte(input), &data)
if err != nil {
    log.Fatal(err)
}
fmt.Println(data.Key1)
fmt.Printf("%+v", data)

Final notes

Keys in the JSON text start with lowercase letters, struct field names in Go start with uppercase letters (needed in order to be exported), but the json package is "clever" enough to match them. Should you use something different, you could use tags to specify how a struct field can be found in the json, e.g.

type ChildStruct1 struct {
    Key3 string `json:"myKey3"`
    Key4 string `json:"myKey4"`
}