Golang使用不同的结构标签进行解组

huangapple go评论83阅读模式
英文:

Golang Unmarshal with different sets of struct tags

问题

我正在使用一个第三方工具的API,并且它的JSON中包含自定义的键名。我还必须在两个不同的环境(生产和暂存)中使用该API。不幸的是,API中的自定义字段在这两个环境中具有不同的键名来表示相同的数据。在下面的示例中,生产环境中的json键custom-1与暂存环境中的json键custom-7完全等效。我想将其中任何一个解组成相同的数据结构,但我不知道如何做到。我希望有一种方法可以覆盖json.Unmarshal()函数使用的标签,在生产环境中使用json,在暂存环境中使用jsonStaging。对我来说,这是最合理和最简单的解决方案。我猜我必须为我的jsonObj类型编写一个自定义的UnmarshalJSON(data []byte) error函数,但我不知道如何在自定义函数中实现所需的行为。有人可以指导我一下,提供一些文档或示例供我参考吗?

当我使用go run运行时,我得到以下结果:

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

这是我当前代码的预期结果,但我希望得到:

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

我无法修改暂存或生产环境的API。

我尝试过创建不同的结构体和接口,但这似乎是一个维护的噩梦,因为字段的数量(因此自定义的JSON键)会增加(它们会增加)。如果那是唯一的方法,请帮助我解决这个问题,因为在我决定这可能不是正确的方法之前,我也没有使其工作。

英文:

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.

答案1

得分: 1

供参考,如果有人想要这样做,我认为我找到了一种使用内置的reflect包的方法。

首先,您必须使用json.Unmarshal()函数,但是要填充一个map[string]interface{}而不是您想要构建的对象。

然后,我编写了一个函数,该函数接受环境和映射作为参数。它遍历您实际对象的所有字段(而不是映射)的新实例,并获取您正在使用的环境的标签。然后,它将新对象中的字段设置为objMap[tag].(<variable_type>)。一旦使用标签设置了所有字段,它就返回新对象。

这是我的工作代码:

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
}
英文:

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].(&lt;variable_type&gt;). Once all of the fields are set using their tag, it returns the new object.

Here is my working code:

package main
import (
&quot;encoding/json&quot;
&quot;fmt&quot;
&quot;reflect&quot;
)
const (
StagingStructTag    = &quot;jsonStaging&quot;
ProductionStructTag = &quot;json&quot;
)
type jsonObj struct {
Id   string `json:&quot;custom-1&quot; jsonStaging:&quot;custom-7&quot;`
Desc string `json:&quot;custom-2&quot; jsonStaging:&quot;custom-8&quot;`
}
func (i jsonObj) String() string {
return fmt.Sprintf(`{ Id: &quot;%s&quot;, Desc: &quot;%s&quot; }`, i.Id, i.Desc)
}
func main() {
var jsonProd = `{
&quot;custom-1&quot;: &quot;object-a&quot;,
&quot;custom-2&quot;: &quot;test&quot;
}
`
var jsonStaging = `{
&quot;custom-7&quot;: &quot;object-a&quot;,
&quot;custom-8&quot;: &quot;test&quot;
}
`
var env string = &quot;staging&quot;
var jsonObjProd jsonObj
var jsonObjStaging jsonObj
var jsonObjProdMap map[string]interface{}
var jsonObjStagingMap map[string]interface{}
json.Unmarshal([]byte(jsonStaging), &amp;jsonObjStagingMap)
json.Unmarshal([]byte(jsonProd), &amp;jsonObjProdMap)
jsonObjStaging = BuildJsonObj(env, jsonObjStagingMap)
env = &quot;production&quot;
jsonObjProd = BuildJsonObj(env, jsonObjProdMap)
fmt.Println(&quot;Production: &quot;, jsonObjProd)
fmt.Println(&quot;Staging:    &quot;, 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 == &quot;staging&quot; {
structTagName = StagingStructTag
} else if env == &quot;production&quot; {
structTagName = ProductionStructTag
}
for i := 0; i &lt; 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 &quot;Id&quot;:
obj.Id = objMap[tag].(string)
case &quot;Desc&quot;:
obj.Desc = objMap[tag].(string)
}
}
}
return obj
}

huangapple
  • 本文由 发表于 2023年1月6日 04:21:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/75023943.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定