'Unmarshaling nested custom same-type JSON in Go

Given the following JSON

{
   "some": "value"
   "nested": {
     "some": "diffvalue",
     "nested": {
        "some": "innervalue"
     }
   }
}

which roughly translates to this struct:

type Envelope struct {
    some     string         `json:"some"`
    nested   InnerEnvelope  `json:"nested"`
}

where InnerEnvelope is: type InnerEnvelope map[string]interface{}

Running a simple json.Unmarshal([]byte value, &target) does not help here, because of the recursive type nature of the original JSON.

I do not know up front how deeply and under which keys the inner maps will exist, so I cannot declare the types upfront.

The idea is, that using map[string]interface{} as the type is not good enough, since I need the values in the InnerEnvelope to be somehow transformed & typed. Details are not important, but image, I need to cast every value inside the NestedEnvelope of a bool type as a string saying "true" or "false" as opposed of having an actual bool type.

I turned to UnmarshalJSON interface to solve this problem. I can easily do it at the top level like so:

func (m *Envelope) UnmarshalJSON(b []byte) error {
    var stuff noBoolMap
    if err := json.Unmarshal(b, &stuff); err != nil {
        return err
    }
    for key, value := range stuff {
        switch value.(type) {

        case bool:
            stuff[key] = strconv.FormatBool(value.(bool))
        }
    }
    return nil
}

But since the inner json.Unmarshal will already have inner maps parsed as map[string]interface{}, I would need to yet-again to traverse the inner maps, cast them to appropriate type and perform my value transformations.

So my question is: In this case, what is the way this would be approached in Go, and preferably do it in a single-pass?

The expected result of the JSON example above would be:

Envelope {
     some: string
     nested: InnerEnvelope {
       some: string {
       nested: InnerEnvelope {
         some: string
       }
     }
  }


Solution 1:[1]

Given your json, you can do this:

type Envelope struct {
    Some     string         `json:"some"`
    Nested   json.RawMessage  `json:"nested"`
}

json.RawMessage is a rather hidden gem, and more people seem to go for the map[string]interface{}.

Using json.RawMessage will result in the nested json to be represented by this RawMessage, which you then can process again as a normal json (unmarshal it into Envelope).

This is more elegant than the map[string]interface{} approach.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1