Golang unmarshal JSON response, then convert the field name into snake_case

2.2k views Asked by At

I want to get data with json tag whose source has PascalCase format and save it to my database. But before going into the database, I want to change the PascalCase format to snake_case format.

My question seems to be the opposite of this question (Golang Unmarshal an JSON response, then marshal with Struct field names). But instead of having PascalCase, I want to have snake_case in the name field

Here is the sample code that i wrote:

package main

import (
    "encoding/json"
    "log"
)

// models data to save in DB
type (
    Person struct {
        FirstName string      `json:"FirstName"`
        LastName  string      `json:"LastName"`
        Children  []ChildData `json:"Children,omitempty"`
    }
    ChildData struct {
        ChildName string `json:"ChildName"`
        Age       int    `json:"Age"`
        FavColor  string `json:"FavColor"`
    }
    PersonOut struct {
        FirstName string      `json:"first_name"`
        LastName  string      `json:"last_name"`
        Children  []ChildData `json:"children,omitempty"`
    }
    ChildDataOut struct {
        ChildName string `json:"child_name"`
        Age       int    `json:"age"`
        FavColor  string `json:"fav_Color"`
    }
)

// grisha is data from fetch API
var grisha = map[string]interface{}{
    "FirstName": "Grisha",
    "LastName":  "Jeager",
    "Children": []map[string]interface{}{
        {
            "ChildName": "Zeke",
            "Age":       2,
            "FavColor":  "blue",
        }, {
            "ChildName": "Eren",
            "Age":       3,
            "FavColor":  "red",
        },
    },
}

func getJsonfromAPI(data map[string]interface{}) []byte {
    grishaJson, _ := json.MarshalIndent(grisha, "", "    ")
    return grishaJson
}

func parsingJSON(jsonInput []byte) {
    var person Person
    json.Unmarshal(jsonInput, &person)

    out := PersonOut(person)
    payload2, err := json.MarshalIndent(out, "", "    ")
    if err != nil {
        panic(err)
    }
    log.Println(string(payload2))
}

func main() {
    data := getJsonfromAPI(grisha)
    parsingJSON(data)
}

The result that I want is like

 {
    "first_name": "Grisha",
    "last_name": "Jeager",
    "children": [
        {
            "child_name": "Zeke",
            "age": 2,
            "fav_color": "blue"
        },
        {
            "child_name": "Eren",
            "age": 3,
            "fav_color": "red"
        }
    ]
}

but the result is still having PascalCase in the nested field:

 {
    "first_name": "Grisha",
    "last_name": "Jeager",
    "children": [
        {
            "ChildName": "Zeke",
            "Age": 2,
            "FavColor": "blue"
        },
        {
            "ChildName": "Eren",
            "Age": 3,
            "FavColor": "red"
        }
    ]
}

I was wondering how to convert field name for the nested structure from unmarshal JSON.

2

There are 2 answers

0
mkopriva On BEST ANSWER

Converting one struct to another works only if their their underlying types (ignoring tags) are identical. If the underlying types are not identical you cannot do the conversion.

Take for example the structs S1 and S2, in the following snippet their underlying types are identical and you can convert one to the other:

type S1 struct {
    Field T1
}
type S2 struct {
    Field T1
}
type T1 string

_ = S2(S1{}) // ok

However, in the next snippet their underlying types are NOT identical and therefore you cannot convert one to the other:

type S1 struct {
    Field T1
}
type S2 struct {
    Field T2
}
type T1 string
type T2 string

_ = S2(S1{}) // cannot convert S1{} (value of type S1) to type S2

For more details read the spec on conversions here: https://go.dev/ref/spec#Conversions


So to convert between two struct whose underlying types are NOT identical you have to manually do the conversion field by field. However, in the case of JSON marshaling, you can make this a bit nicer by implementing the json.Marshaler interface and have the individual instances "convert themselves".

type Person struct {
    FirstName string      `json:"FirstName"`
    LastName  string      `json:"LastName"`
    Children  []ChildData `json:"Children"`
}

func (p Person) MarshalJSON() ([]byte, error) {
    type T struct {
        FirstName string      `json:"first_name"`
        LastName  string      `json:"last_name"`
        Children  []ChildData `json:"children"`
    }

    return json.Marshal(T(p))
}

type ChildData struct {
    ChildName string `json:"ChildName"`
    Age       int    `json:"Age"`
    FavColor  string `json:"FavColor"`
}

func (d ChildData) MarshalJSON() ([]byte, error) {
    type T struct {
        ChildName string `json:"child_name"`
        Age       int    `json:"age"`
        FavColor  string `json:"fav_color"`
    }

    return json.Marshal(T(d))
}

https://go.dev/play/p/UGJY5p490Gs

0
Jovi On

https://go.dev/play/p/l4O8KBNrm1T

We need to:

  1. Mapping Person to PersonOut
  2. Mapping ChildData to ChildDataOut
func childDataToChildDataOut(childData ChildData) ChildDataOut {
    return ChildDataOut{
        ChildName: childData.ChildName,
        Age:       childData.Age,
        FavColor:  childData.FavColor,
    }
}

func personToPersonOut(person Person) PersonOut {
    var children []ChildDataOut

    for _, childData := range person.Children {
        childDataOut := childDataToChildDataOut(childData)
        children = append(children, childDataOut)
    }

    return PersonOut{
        FirstName: person.FirstName,
        LastName:  person.LastName,
        Children:  children,
    }
}