POSTS

JSON Parsing With Golang

Challenge:

When using Go, we can’t be quite as “loosey-goosey” with parsing JSON compared to using Ruby or JS. The challenge/lesson I am trying to solve/learn is how to parse API responses that arent always straight forward or a 1:1 mapping to a given data struct in go. By leveraging json.RawMessage in our code, we have a tool at our disposal to make this scenario much easier to deal with.

Credit for this “Note to self” goes to both:

There are many great examples out there in addition to these - but these 2 made the most sense to me as I learn more about Go.

Opening Definitions

Note the structs used for this code to marshal & store data

package main

import (
	"encoding/json"
	"fmt"
)

// Computer ...
type Computer struct {
	Type  string `json:"type"`
	Model string `json:"model"`
}

// Mammal ...
type Mammal struct {
	Type string `json:"type"`
	Name string `json:"name"`
}

// Message ...
type Message struct {
	Type string `json:"type"`
	Data json.RawMessage
}

// Response ...
type Response struct {
	Mammal   *Mammal   `json:"mammal,omitempty"`
	Computer *Computer `json:"computer,omitempty"`
}

By using pointers in the Response struct as well as setting omitempty, we don’t have to worry about including any additonal attributes not set when marshalling the response struct to JSON.

Main execution

Here, we will loop through an array containing 2 json data structures.

func main() {
	var a [2][]byte

	a[0] = []byte(`{
		"type": "computer",
		"data": {
			 "type": "droid",
			 "model": "C3PO"
		}}`)

	a[1] = []byte(`{
		"type": "mammal",
		"data": {
				"type": "homosapien",
				"name": "Fred"
		}}`)

	for i := 0; i < len(a); i++ {
		if err := parseMessage(a[i]); err != nil {
			fmt.Println(err)
		}
	}
}

Parsing logic

For a given slice of bytes (JSON data), our goal is to use data at a higher level in the payload (type in this case) to determine how to parse the remainder of the JSON payload.

func parseMessage(JSONData []byte) error {
	var m Message
	var r Response

	// Unmarshal JSONData to Message struct first
	if err := json.Unmarshal(JSONData, &m); err != nil {
		return err
	}

	// Based on the "type" value, unmarshal accordingly
	switch m.Type {
	case "computer":
		var o Computer
		if err := json.Unmarshal([]byte(m.Data), &o); err != nil {
			return err
		}
		fmt.Printf("I am a %s.  My model is: %s.\n", o.Type, o.Model)
		r.Computer = &o
	case "mammal":
		var o Mammal
		if err := json.Unmarshal([]byte(m.Data), &o); err != nil {
			return err
		}
		fmt.Printf("I am a %s.  My name is: %s.\n", o.Type, o.Name)
		r.Mammal = &o
	default:
		fmt.Println("unable to unmarshal JSON data or differentiate the type")
	}

	j, err := json.Marshal(r)
	if err != nil {
		return err
	}

	fmt.Println(string(j))
	return nil
}