简体   繁体   中英

Golang Unmarshal with different sets of struct tags

I am working with an API for a third party tool and it contains custom key names in it's JSON. I also have to work with the API on two different environments (prod and staging). Unfortunately, the custom fields in the API have different key names on the two environments to represent the same data. In the example below, the json key custom-1 on production is exactly equivalent to the json key custom-7 on staging. I want to unmarshal either one into the same data structure, but I do not know how. I am hoping there is a way to somehow override the tag that the json.Unmarshal() function uses to use json on Prod, but use jsonStaging on staging. To me, that is the solution that makes the most sense and would be simplest. I'm guessing I have to write a custom UnmarshalJSON(data []byte) error function for my jsonObj type, but again, I don't know how to achieve the desired behavior in the custom function. Can someone point me in the right direction, to some documentation, or to some examples that I can use?

package main

import (
    "encoding/json"
    "fmt"
)

type jsonObj struct {
    Id   string `json:"custom-1" jsonStaging:"custom-7"`
    Desc string `json:"custom-2" jsonStaging:"custom-8"`
}

func (i jsonObj) String() string {
    return fmt.Sprintf(`{ Id: "%s", Desc: "%s" }`, i.Id, i.Desc)
}

func main() {
    var jsonProd = `{
        "custom-1": "object-a",
        "custom-2": "test"
    }
    `
    var jsonStaging = `{
        "custom-7": "object-a",
        "custom-8": "test"
    }
    `

    var jsonObjProd jsonObj
    var jsonObjStaging jsonObj

    json.Unmarshal([]byte(jsonProd), &jsonObjProd)
    json.Unmarshal([]byte(jsonStaging), &jsonObjStaging)

    fmt.Println("Production: ", jsonObjProd)
    fmt.Println("Staging: ", jsonObjStaging)
}

When I run this with go run, I get

Production:  { Id: "object-a", Desc: "test" }
Staging:  { Id: "", Desc: "" }

That is expected with my current code, but I would like to get

Production:  { Id: "object-a", Desc: "test" }
Staging:  { Id: "object-a", Desc: "test" }

I do not have the ability to modify the API for either staging or production environments.

I have tried creating different structs and interfaces, but this seems like a maintenance nightmare as the number of fields (and therefore custom json keys) increase (they will). If that is the only way, please help me with that as well as I did not get that to work either before I decided that it was probably not the correct path.

For future reference if anyone is looking to do this, I think I found a way using the built-in reflect package.

First, you have to use the json.Unmarshal() function, but populate a map[string]interface{} instead of the object you want to build.

I then wrote a function that takes the environment and the map. It goes through all of the fields in a new instance of your actual object (not the map) and gets the tag for the environment you're using. Then it sets the field in the new object to objMap[tag].(<variable_type>) . Once all of the fields are set using their tag, it returns the new object.

Here is my working code:

package main

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

const (
    StagingStructTag    = "jsonStaging"
    ProductionStructTag = "json"
)

type jsonObj struct {
    Id   string `json:"custom-1" jsonStaging:"custom-7"`
    Desc string `json:"custom-2" jsonStaging:"custom-8"`
}

func (i jsonObj) String() string {
    return fmt.Sprintf(`{ Id: "%s", Desc: "%s" }`, i.Id, i.Desc)
}

func main() {
    var jsonProd = `{
        "custom-1": "object-a",
        "custom-2": "test"
    }
    `
    var jsonStaging = `{
        "custom-7": "object-a",
        "custom-8": "test"
    }
    `

    var env string = "staging"
    var jsonObjProd jsonObj
    var jsonObjStaging jsonObj
    var jsonObjProdMap map[string]interface{}
    var jsonObjStagingMap map[string]interface{}

    json.Unmarshal([]byte(jsonStaging), &jsonObjStagingMap)
    json.Unmarshal([]byte(jsonProd), &jsonObjProdMap)

    jsonObjStaging = BuildJsonObj(env, jsonObjStagingMap)
    env = "production"
    jsonObjProd = BuildJsonObj(env, jsonObjProdMap)

    fmt.Println("Production: ", jsonObjProd)
    fmt.Println("Staging:    ", jsonObjStaging)
}

func BuildJsonObj(env string, objMap map[string]interface{}) jsonObj {
    var obj jsonObj
    var t reflect.Type = reflect.TypeOf(obj)
    var structTagName string

    if env == "staging" {
        structTagName = StagingStructTag

    } else if env == "production" {
        structTagName = ProductionStructTag
    }

    for i := 0; i < t.NumField(); i++ {
        var field reflect.StructField = t.Field(i)
        var tag string
        var ok bool

        if tag, ok = field.Tag.Lookup(structTagName); ok {
            switch field.Name {
            case "Id":
                obj.Id = objMap[tag].(string)
            case "Desc":
                obj.Desc = objMap[tag].(string)
            }
        }

    }
    return obj
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM