logo

Lucas Katayama/Postpone JSON Processing

Created Sat, 05 Dec 2020 21:24:42 -0300 Modified Sat, 05 Dec 2020 21:24:42 -0300
375 Words

This posts presents a use case for json.RawMessage from encoding/json package.

Imagine you have an API for a fast food restaurant which process the following JSON:

[
    {
        "Type": "Cheeseburguer",
        "Params": {
            "ExtraCheese": true
        }
    },
    {
        "Type": "Coke",
        "Params": {
            "Diet": false,
            "Size": "L"
        }
    }, 
    {
        "Type": "French Fries",
        "Params": {
            "Size": "S",
            "Ketchup": true
        }
    }
]

A first attempt to model that, you will need the following structs to unmarshal that JSON:

type CheeseburguerParams struct {
    ExtraCheese bool
}

type CokeParams struct {
    Diet bool
    Size string
}

type FrenchFriesParams struct {
    Size string
    Ketchup bool
}

type Item struct {
    Type   string
    Params map[string]interface{}
}

type Order struct {
    Items []Item
}

To process specifi Item params you have 2 options:

  1. Read and build params for each type
  2. Marshal map[string]interface{} to []byte and based on type, unmarshal it to its specific param

The first one you have to create a function that reads a generic map[string]interface{} and populate a param.

The second one is like JSON.stringify(JSON.parse()) from Javascript. It is easier, because you don’t have to worry about type casting, populate, etc. But it takes an extra json.Marshal and json.Unmarshal.

Let’s take a lazy approach without doing an extra json.Marshal.

Here comes json.RawMessage.

If you model the Item struct like this:

type Item struct {
    Type   string
    Params json.RawMessage
}

The json.Unmarshal puts the []byte inside the Params field.

Than you just need to switch on Type field and do the last json.Unmarshal:

for _, item := range order.Items {
    switch item.Type {
        case "Cheeseburger":
        var param CheeseburgerParam
        if err := json.Unmarshal(item.Params, &param); err != nil {
            log.Error(err)
        }
        processCheeseburguer(param)
        
        case "French Fries":
        var param FrenchFriesParam
        if err := json.Unmarshal(item.Params, &param); err != nil {
            log.Error(err)
        }
        processFrenchFries(param)

        case "Coke":
        var param CokeParam
        if err := json.Unmarshal(item.Params, &param); err != nil {
            log.Error(err)
        }
        processCoke(param)
    }
}

That is easier to process dynamic messages with parameters from a list, or message broker, or anything else, by postponing the processing of the extra information using json.RawMessage.

The interesting part, is that it don’t work if you use []byte type for Param field. Because json.Unmarhsal tries to reads an array which is not there in the JSON.

Check this: https://play.golang.org/p/xUIsUB8GAi7

References