英文:
JSON Unmarshalling with different keys
问题
我正在尝试从不同的来源解析一些 JSON 数据,这些数据可能具有不同的键。例如,我可能有以下 JSON 数据:
{
"a": 1,
"b": 2
}
或者我可能有:
{
"c": 1,
"b": 2
}
在这种情况下,我可以保证 "b" 键一定存在。然而,我希望以相同的方式表示 "a" 和 "c"。实际上,我想要的是:
type MyJson struct {
Init int json:"a" json:"c"
Sec int json:"b"
}
基本上,我希望解析器查找这两个键,并将其设置为 Init
。但是这实际上不起作用(否则我就不会发帖了)。解析第一个 JSON 数据可以得到我想要的结果,而解析第二个 JSON 数据会将 Init
设置为 0。我理想的选项是将其解析为一个结构体,有两种可能性:
- 我像上面那样使用多个标签表示结构体
- 我定义多个结构体,并能够选择解析到哪一个
我尝试通过创建两个不同的结构体和一个映射来实现第二种方法,但是似乎我不能将类型作为映射的第二个值:
type MyJson1 struct {
Init int json:"a"
Sec int json:"b"
}
type MyJson2 struct {
Init int json:"c"
Sec int json:"b"
}
有没有一种方法可以定义一组行为类似于接口的结构体?也就是说,它们都具有相同的字段,但是定义方式不同。或者也许还有其他方法。我可以将字段解析为其他字段,然后相应地设置 Init
。但是这种方法在有多个变体时不具有可扩展性。
谢谢!
英文:
I'm trying to unmarshal some JSON from different sources that may have different keys. For instance, I may have:
{
"a": 1,
"b": 2
}
Or I may have:
{
"c": 1,
"b": 2
}
In this case, I can guarantee that "b" will be there. However, I want "a" and "c" to be represented the same way. In effect, what I want is:
type MyJson struct {
Init int `json:"a",json:"c"`
Sec int `json:"b"
}
Basically I want the unmarshaller to look for either key and set it as Init
. Now this doesn't actually work (or I wouldn't be posting). Unmarshalling the first gives me what I want, while the second sets Init
to 0. My ideal option would be to unmarshal to a struct, with one of two possibilities:
- I represent the struct with multiple tags as above
- I define multiple structs, but am able to choose which to unmarshal to
I tried to implement number 2 by making two different structs and a map, but it seems I can't make a map with a type as the second value:
type MyJson1 struct {
Init int `json:"a"`
Sec int `json:"b"`
}
type MyJson2 struct {
Init int `json:"c"`
Sec int `json:"b"`
}
Is there a way to define a set of structs that behaves like an interface? That is, they all have the same fields, but are defined differently. Or maybe there's another way. I could make the fields unmarshalling "a" and "c" other fields, then set Init
accordingly. But this doesn't scale beyond a couple of variants.
Thanks!
答案1
得分: 5
icza的方法很好,如果这个实现能够匹配stdlib的接口就更好了。
我建议实现json.Unmarshaler
:
// 无法获取c本身。
type MyJson struct {
A int `json:"a"`
B int `json:"b"`
}
func (j *MyJson) UnmarshalJSON(b []byte) error {
type Alias MyJson
realValue := struct {
*Alias
C int `json:"c"` // <- 现在它可以接受c的值
}{(*Alias)(j), 0}
if err := json.Unmarshal(b, &realValue); err != nil {
return err
} else if realValue.C != 0 {
// 如果C有值,覆盖A
realValue.A = realValue.C
}
return nil
}
然后使用json.Unmarshal
进行解码。
英文:
icza's approach is good, and better if this implementation could match stdlib's interface.
I suggest implementing json.Unmarshaler
:
// it can't get c itself.
type MyJson struct {
A int `json:"a"`
B int `json:"b"`
}
func (j *MyJson) UnmarshalJSON(b []byte) error {
type Alias MyJson
realValue := struct {
*Alias
C int `json:"c"` // <- now it can accept 'c' value
}{(*Alias)(j), 0}
if err := json.Unmarshal(b, &realValue); err != nil {
return err
} else if realValue.C != 0 {
// if C has value, overwrite A
realValue.A = realValue.C
}
return nil
}
And just decode it using json.Unmarshal
.
答案2
得分: 4
一种可能的方法是定义一个struct
,其中包含所有可能输入的变体字段,并为此结构提供一个方法,该方法将返回在输入中找到的字段:
type MyJson struct {
A *int `json:"a"`
C *int `json:"c"`
Sec int `json:"b"`
}
func (j *MyJson) Init() int {
if j.A == nil {
return *j.C
}
return *j.A
}
使用它:
inputs := []string{
`{"a": 1, "b": 2}`,
`{"c": 1, "b": 2}`,
}
for _, input := range inputs {
var my MyJson
if err := json.Unmarshal([]byte(input), &my); err != nil {
panic(err)
}
fmt.Printf("Init: %v, Sec: %v\n", my.Init(), my.Sec)
}
输出如预期的那样(在Go Playground上尝试):
Init: 1, Sec: 2
Init: 1, Sec: 2
一个小技巧:##
在原始结构中,我们为两个可能的变体添加了2个字段。我将它们定义为指针,以便我们可以检测到在JSON输入中找到了哪一个。现在,在解组之前,如果我们将这些指针设置为指向相同的值,那就是我们所需要的:无论使用哪个输入JSON的变体,相同的值将被设置在内存中,因此您始终可以只读/引用Init
结构字段:
type MyJson struct {
Init *int `json:"a"`
Init2 *int `json:"c"`
Sec int `json:"b"`
}
func main() {
inputs := []string{
`{"a": 1, "b": 2}`,
`{"c": 1, "b": 2}`,
}
for _, input := range inputs {
var my MyJson
my.Init = new(int) // 分配一个int
my.Init2 = my.Init // 将Init2设置为指向相同的值
if err := json.Unmarshal([]byte(input), &my); err != nil {
panic(err)
}
fmt.Printf("Init: %v, Sec: %v\n", *my.Init, my.Sec)
}
}
在Go Playground上尝试。
您可以创建一个函数,用于创建和设置准备进行解组的MyJson
,如下所示:
func NewMyJson() (my MyJson) {
my.Init = new(int) // 分配一个int
my.Init2 = my.Init // 将Init2设置为指向相同的值
return
}
因此,使用它变得非常简单:
var my = NewMyJson()
err := json.Unmarshal([]byte(input), &my)
另一个关于被欺骗变体的技巧:##
您可以指定Init
字段不是指针,因为Init2
是指针并且指向Init
就足够了,因此这变得更加简单和理想(但是NewMyJson
必须返回一个指针):
type MyJson struct {
Init int `json:"a"`
Init2 *int `json:"c"`
Sec int `json:"b"`
}
func NewMyJson() *MyJson {
my := new(MyJson)
my.Init2 = &my.Init // 将Init2设置为指向Init
return my
}
英文:
One possibility is to define a struct
which has a field for all the variants for your possible inputs, and for convenience provide a method for this struct which will return the field that was found in the input:
type MyJson struct {
A *int `json:"a"`
C *int `json:"c"`
Sec int `json:"b"`
}
func (j *MyJson) Init() int {
if j.A == nil {
return *j.C
}
return *j.A
}
Using it:
inputs := []string{
`{"a": 1, "b": 2}`,
`{"c": 1, "b": 2}`}
for _, input := range inputs {
var my MyJson
if err := json.Unmarshal([]byte(input), &my); err != nil {
panic(err)
}
fmt.Printf("Init: %v, Sec: %v\n", my.Init(), my.Sec)
}
Output, as expected (try it on the Go Playground):
Init: 1, Sec: 2
Init: 1, Sec: 2
##And a Little Trick:##
In the original struct we added 2 fields for the 2 possible variants. I defined them as pointers so we can detect which one was found in the JSON input. Now if before unmarshalling we set these pointers to point to the same value, that's all we need: doesn't matter which variant of the input JSON we use, the same value will be set in memory, so you can always just read/refer the Init
struct field:
type MyJson struct {
Init *int `json:"a"`
Init2 *int `json:"c"`
Sec int `json:"b"`
}
func main() {
inputs := []string{
`{"a": 1, "b": 2}`,
`{"c": 1, "b": 2}`}
for _, input := range inputs {
var my MyJson
my.Init = new(int) // Allocate an int
my.Init2 = my.Init // Set Init2 to point to the same value
if err := json.Unmarshal([]byte(input), &my); err != nil {
panic(err)
}
fmt.Printf("Init: %v, Sec: %v\n", *my.Init, my.Sec)
}
}
Try it on the Go Playground.
You can create a function which creates and sets up your MyJson
ready for unmarshalling like this:
func NewMyJson() (my MyJson) {
my.Init = new(int) // Allocate an int
my.Init2 = my.Init // Set Init2 to point to the same value
return
}
And so using it becomes this simple:
var my = NewMyJson()
err := json.Unmarshal([]byte(input), &my)
Another Trick on the Tricked variant:
You can specify the Init
field not to be a pointer because it is enough for Init2
to be a pointer and point to Init
, so this becomes even more simple and desirable (but then NewMyJson
must return a pointer):
type MyJson struct {
Init int `json:"a"`
Init2 *int `json:"c"`
Sec int `json:"b"`
}
func NewMyJson() *MyJson {
my := new(MyJson)
my.Init2 = &my.Init // Set Init2 to point to Init
return my
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论