Home > Net >  How to flatten a Struct with embedded structs to json
How to flatten a Struct with embedded structs to json

Time:09-30

Given the following struct types, StructA and StructB that are embedded in CompleteStruct

type StructA struct {
    A int `json:"a_a"`
    B int `json:"a_b"`
    C int `json:"a_c"`
}
type StructB struct {
    A int `json:"b_a"`
    B int `json:"b_b"`
}

type CompleteStruct struct {
    Name string `json:"name"`
    StructA
    StructB
}

And s which is a new struct.

s := CompleteStruct{Name: "Example",
    StructA: StructA{
        A: 1, 
        B: 2, 
        C: 3,
    },
    StructB: StructB{
        A: 4,
        B: 5,
    },
}

How do you transform s to the following json.

[
  {
    "name": "Example",
    "field": "a_a",
    "value": 1
  },
  {
    "name": "Example",
    "field": "a_b",
    "value": 2
  },
  {
    "name": "Example",
    "field": "a_c",
    "value": 3
  },
  {
    "name": "Example",
    "field": "b_a",
    "value": 4
  },
  {
    "name": "Example",
    "field": "b_b",
    "value": 5
  }
]

Note: In reality, CompleteStruct will contain 10 or more embedded structs and each embedded struct will contain 10 or more fields. So I would like a solution that does not require typing each field out individually, I assume this will require using reflection

CodePudding user response:

You can't solve it without reflection. Simple example:

func (u *CompleteStruct) MarshalJSON() ([]byte, error) {
    type Result struct {
        Name  string `json:"name"`
        Field string `json:"field"`
        Value any    `json:"value"`
    }
    
    var res []Result
    val := reflect.ValueOf(u).Elem()
    for i := 0; i < val.NumField(); i   {
        field := val.Field(i)
        switch field.Kind() {
        case reflect.Struct:
            for i := 0; i < field.NumField(); i   {
                tp := field.Type().Field(i)
                field := field.Field(i)
                res = append(res, Result{
                    Name:  u.Name,
                    Field: tp.Name,
                    Value: field.Interface(),
                })
            }
        }
    }
    return json.Marshal(res)
}

PLAYGROUND

CodePudding user response:

This should give you the structure you want:

package main

import (
    "encoding/json"
    "os"
    "reflect"
)

type StructA struct {
    A int `json:"a_a"`
    B int `json:"a_b"`
    C int `json:"a_c"`
}
type StructB struct {
    A int `json:"b_a"`
    B int `json:"b_b"`
}

type CompleteStruct struct {
    Name string `json:"name"`
    StructA
    StructB
}

func main() {
    s := CompleteStruct{Name: "Example",
        StructA: StructA{
            A: 1,
            B: 2,
            C: 3,
        },
        StructB: StructB{
            A: 4,
            B: 5,
        },
    }

    flat(s)

    json.NewEncoder(os.Stdout).Encode(results)
}

type resp struct {
    Name  string `json:"name"`
    Field string `json:"field"`
    Value any    `json:"value"`
}

var globalName string
var results []resp

func flat(s interface{}) {
    st := reflect.TypeOf(s)
    for i := 0; i < st.NumField(); i   {
        field := st.Field(i)
        if field.Type.Kind() == reflect.Struct {
            flat(reflect.ValueOf(s).Field(i).Interface())
        } else {
            name := field.Tag.Get("json")
            if name == "name" {
                globalName = reflect.ValueOf(s).Field(i).String()
                continue
            }
            results = append(results, resp{Name: globalName, Field: name, Value: reflect.ValueOf(s).Field(i).Interface()})
        }
    }
}
go run ./main.go | jq '.'
  • Related