Search code examples
jsongostructmarshalling

How to marshal nested struct to flat JSON


I am trying to marshall a nested struct with several of the same structs to a flat JSON structure E.G.

type A struct {
    Value float64
    Unit  string
}
type B struct {
    p1 string `json:p1`
    p2 int    `json:p1`
    ...
    a1 A      `json:"a1,omitempty"`
    a2 A      `json:"a1,omitempty"`
    ...
}

When calling json.Marshall(B) the goal is to get a json structure that is using the above code to print a flat structure. So that instead of

{
    "p1": "...",
    "p2": 1,
    ...
    "a1": {...}
    "a2": {...}
    ...
}

I would get a structure similar to

{
    "p1": "...",
    "p2": 1,
    ...
    "a1": 1.1,
    "a1_u": "unit",
    "a2": 1.2,
    "a2_u": "unit",
    ...
}

I have looked at the concept of embedding, but that would only work if there is only one A in B. And would still not fully accomplish what i wish.

The reason for the need of this structure in the JSON is sadly not negotiable. And the structs that need to be used will contain a large amount of A{}. It is known what they will contain, so creating and using a struct as below is an option. But as the project grows, it only serves to confuse and create a maintenance hell.

type C struct {
    p1 string `json:p1`
    p2 int    `json:p1`
    ...
    a1   float64 `json:"a1,omitempty"`
    a1_u string  `json:"a1_u,omitempty"`
    a2   float64 `json:"a2,omitempty"`
    a2_U string  `json:"a2_u,omitempty"`
    ...
}

There is the option of writing custom marshalling for B{} and A{} which could probably accomplish what I need, the issue is that we need a bunch of different structs like B{} that are similar enough that I believe there should be a way to generalise a solution to this.

Thoughts go to using something relating to reflections. But as I am quite new to GO I have not managed to figure out the solution. And all efforts spent googling only shows me the opposite.

Thanks in advance for any help, event if it is "It cant be done".

*EDIT

B and by extension C is meant to be able to contain an arbitrary number of A as well as some primitives. In the future there can be type D and E as well, that each contain a non common amount of A and primitives. Or in fact any number of structs with these constraints. What I am trying to avoid is writing a Marhaller for each new struct.


Solution

  • Write a custom marshaler. Example:

    type A struct {
        Value float64
        Unit  string
    }
    
    type B struct {
        a1 A
        a2 A
    }
    
    func (b *B) MarshalJSON() ([]byte, error) {
        intermediate := map[string]interface{}{
            "a1":   b.a1.Value,
            "a1_u": b.a1.Unit,
            "a2":   b.a2.Value,
            "a2_u": b.a2.Unit,
        }
        return json.Marshal(intermediate)
    }
    

    Depending on how you expect this code to grow/expand over time, you may be able to use some kind of loop, or reflection, but without knowing the extension plans, it's impossible to be specific.