# Unleashing the power of Go: How to Unmarshal Dynamic JSON

Today we are going to write our own custom `json.Unmarshal()` method. I find it very useful and can make our code more maintainable and readable if we can make good use of type alias and the `json.Unmarshal` method. Let's say we want to decode a JSON-encoded data take from a notification service where it sends us different events with different underlying data structure. Here is an example json of events:

```json
{"resource_type":"payment","action":"confirmed","data":{"amount":100}}
{"resource_type":"customer","action":"created","data":{"name":"john"}}
```

We have some basic fields like `resource_type` and `action` that are the same for all events, but the type of data is different depending on the `resource_type`. For example, the `payment` resource has an `amount` field of type number, and this is obviously different from the other resource type, `customer`, where it has a `name` field of type string.

## Built-in JSON Unmarshal Method

The `json.Unmarshal()` method in Golang is used to decode a JSON-encoded data structure into a Golang struct or map. It takes two parameters - a byte slice containing the JSON-encoded data, and a pointer to the struct or map that the decoded data will be stored in. How do we do that with the built-in method `json.Unmarshal`. Let's take a look an example below:

```go
package main

import (
	"encoding/json"
	"fmt"
)

type Event struct {
	ResourceType string `json:"resource_type"`
	Action       string `json:"action"`
	Data         any    `json:"data"`
}

func main() {
	var jsonBlob = []byte(`[
	{"resource_type":"payment","action":"confirmed","data":{"amount":100}},
	{"resource_type":"customer","action":"created","data":{"name":"john"}}
]`)
	
	var events []Event
	err := json.Unmarshal(jsonBlob, &events)
	if err != nil {
		fmt.Println("error:", err)
	}
	fmt.Printf("%+v", events)
	// output: [{ResourceType:payment Action:confirmed Data:map[amount:100]} {ResourceType:customer Action:created Data:map[name:john]}]
}
```

As you can see from the example above, we set the `Data` field to type `any` as we don't know what type of data we will get from the notification service. When we let the program decode the data field, we get the value as `map[string]interface{}`. We may get away with this since we can still access the data in the map by key, depending on the resource type. However, this method has the disadvantage of making the code less readable and maintainable, because we don't know the structure of the data field. In addition, we cannot take advantage of the go package `validator`, which validates structures and individual fields based on tags. You can see more details about [Validator](https://pkg.go.dev/github.com/go-playground/validator#section-readme).

We would need to know exactly what type of data we want go to decode, but we don't know that until go has received the data. Is there a way to decode the `resource_type` field first, before decoding everything, so that we know for sure what type of data we are decoding to? The answer is yes, but we will have to create our own custom `UnmarshalJSON` method. To see more details about [UnmarshalJSON](https://pkg.go.dev/encoding/json#Unmarshaler).

## Unmarshal Dynamic Data

Here is the example for creating our own custom `UnmarshalJSON` method:

```go
type Event struct {
	ResourceType string `json:"resource_type"`
	Action       string `json:"action"`
	Data         any    `json:"data"`
}

type Customer struct {
	Name string `json:"name"`
}

type Payment struct {
	Amount int `json:"amount"`
}

func (e *Event) UnmarshalJSON(data []byte) error {
	var inner struct {
		ResourceType string `json:"resource_type"`
	}
	if err := json.Unmarshal(data, &inner); err != nil {
		return err
	}

	switch inner.ResourceType {
	case "payment":
		e.Data = new(Payment)
	case "customer":
		e.Data = new(Customer)
	}

	type aka Event
	return json.Unmarshal(data, (*aka)(e))
}
```

In this example we define a `Customer` struct which has a field - `Name` and a `Payment` struct which has a field - `Amount`. We then create an `inner` struct to get the `ResourceType` in our custom `UnmarshalJSON` method, so that we can have a switch statement to decide which `struct` we want our go program to decode into depending on the resource type. Once we have assigned our data to the appropriate structure, we can call the `json.Unmarshal()` method to decode the JSON string into the receiver `e`. You may notice that we declare an alias `type aka Event` instead of using `Event` directly, this is because we are trying to avoid an infinite loop with a custom `UnmarshalJSON` method.

A type alias is a way of defining a new name for an existing type. However, it does not inherit the methods of the `Event` type, so we can get around that by using an alias.

Let's put them all together and see the result below:

```go
package main

import (
	"encoding/json"
	"fmt"
)

type Event struct {
	ResourceType string `json:"resource_type"`
	Action       string `json:"action"`
	Data         any    `json:"data"`
}

func (e *Event) UnmarshalJSON(data []byte) error {
	var inner struct {
		ResourceType string `json:"resource_type"`
	}
	if err := json.Unmarshal(data, &inner); err != nil {
		return err
	}

	switch inner.ResourceType {
	case "payment":
		e.Data = new(Payment)
	case "customer":
		e.Data = new(Customer)
	}

	type eventAlias Event
	return json.Unmarshal(data, (*eventAlias)(e))
}

type Customer struct {
	Name string `json:"name"`
}

func (c *Customer) String() string {
	return fmt.Sprintf("{Name:%s}", c.Name)
}

type Payment struct {
	Amount int `json:"amount"`
}

func (p *Payment) String() string {
	return fmt.Sprintf("{Amount:%d}", p.Amount)
}

func main() {
	var jsonBlob = []byte(`[
	{"resource_type":"payment","action":"confirmed","data":{"amount":100}},
	{"resource_type":"customer","action":"created","data":{"name":"john"}}
]`)

	var events []Event
	err := json.Unmarshal(jsonBlob, &events)
	if err != nil {
		fmt.Println("error:", err)
	}
	fmt.Printf("%+v", events)
	// output: [{ResourceType:payment Action:confirmed Data:{Amount:100}} {ResourceType:customer Action:created Data:{Name:john}}]
}
```

Before we try out our new custom `UnmarshalJSON` method, we add an extra custom `String` method to these two types, `Customer` and `Payment`, so that our result can be printed more nicely. Moreover, we can be certain that they are decoded to corresponding types. Now, we can call the `json.Unmarshal()` method to decode the `jsonBlob` variable into `events` variable. Finally, we print out the `events` to the console and we get the whole event with our custom type instead of `map[string]interface{}`.

## Conclusion

By writing our own custom `UnmarshalJSON` method, we have more granular control over how the data is decoded into our own struct, even though the data is dynamic. In addition, we can use the go package `validator` because we have defined our custom struct for each field and subfield.

We learn how to use aliases to prevent recursion calls to the custom method from causing `panic`, although the original design of [Aliases](https://go.dev/talks/2016/refactor.article#TOC_5.1.) is to make code more readable by giving a more descriptive name to a type, or to make it easier to refactor your code later. Please let me know what you think and feel free to leave a comment below :)

Hopefully you found something useful and can apply it to your work.

The source code for this tutorial is available [here](https://github.com/rayspock/mastering-go-examples/blob/0b0b553c53a5aaf6f3f5e70548912f5d3668f3df/json/unmarshaljson.go).

Thank you for reading.
