英文:
How can I implement UnmarshalJSON in this case and only define special behavior for one interface field?
问题
type MyObj struct {
Field1 string `json:"field_1"`
Field2 int64 `json:"field_2"`
Field3 string `json:"field_3"`
...
FieldK string `json:"field_k"`
FieldN MyInterface `json:"field_n"`
}
我在代码中有一个模型,除了与领域无关的细节外,看起来像这样。FieldN
字段的想法是支持两种类型,比如 MyType1
和 MyType2
。它们都有相同的 CommonMethod()
,但模型非常不同,所以不是通过拥有更多字段的父类型来解决问题。
可预见的是,Go 无法将 JSON 反序列化为接口值。我尝试使用自定义的 UnmarshalJSON()
实现,但目前看起来非常笨拙:
func (m *MyObj) UnmarshalJSON(data []byte) error {
out := &MyObj{}
var m map[string]json.RawMessage
if err := json.Unmarshal(data, &m); err != nil {
return err
}
if err := json.Unmarshal(m["field_1"], &out.Field1); err != nil {
return err
}
delete(m, "field_1")
if err := json.Unmarshal(m["field_2"], &out.Field2); err != nil {
return err
}
delete(m, "field_2")
if err := json.Unmarshal(m["field_3"], &out.Field3); err != nil {
return err
}
delete(m, "field_3")
... // 从 3 到 k-1
if err := json.Unmarshal(m["field_k"], &out.FieldK); err != nil {
return err
}
delete(m, "field_k")
var mt1 MyType1
if err := json.Unmarshal(m["field_n"], &mt1); err == nil {
out.FieldN = &mt1
return nil
}
var mt2 MyType2
if err := json.Unmarshal(m["field_n"], &mt2); err == nil {
out.FieldN = &mt2
return nil
}
return nil
}
这种方法的思路是首先反序列化所有“静态”值,然后处理接口类型。然而,我认为它存在至少两个问题:
-
在我的情况下,字段的数量可能在将来增加,代码将变得更加重复。
-
即使是当前版本,也需要检查映射
m
是否具有键field_i
,否则我将只得到unexpected end of input
。这更加麻烦。
是否有更优雅的方法来实现以下操作:
- 反序列化所有具有静态类型的字段
- 处理唯一的特殊接口类型的值
谢谢!
重要更新:
应该注意,Field1
实际上定义了 FieldN
应该使用哪种具体类型。正如评论中指出的那样,这应该大大简化方法,但我仍然在正确实现上有些困难。
英文:
type MyObj struct {
Field1 string `json:"field_1"`
Field2 int64 `json:"field_2"`
Field3 string `json:"field_3"`
...
FieldK string `json:"field_k"`
FieldN MyInterface `json:"field_n"`
}
I have a model in my code that (except for the irrelevant domain details) looks like this. The idea of the FieldN
field is to support two types, say, MyType1
and MyType2
. These have the same CommonMethod()
but the models are very different so it's not about having a parent type with more fields.
Quite expectedly, Go is unable to unmarshal JSON into an interface value. I am trying to use a custom UnmarshalJSON()
implementation but so far it looks really awkward:
func (m *MyObj) UnmarshalJSON(data []byte) error {
out := &MyObj{}
var m map[string]json.RawMessage
if err := json.Unmarshal(data, &m); err != nil {
return err
}
if err := json.Unmarshal(m["field_1"], &out.Field1); err != nil {
return err
}
delete(m, "field_1")
if err := json.Unmarshal(m["field_2"], &out.Field2); err != nil {
return err
}
delete(m, "field_2")
if err := json.Unmarshal(m["field_3"], &out.Field3); err != nil {
return err
}
delete(m, "field_3")
... // from 3 to k-1
if err := json.Unmarshal(m["field_k"], &out.FieldK); err != nil {
return err
}
delete(m, "field_k")
var mt1 MyType1
if err := json.Unmarshal(m["field_n"], &mt1); err == nil {
s.FieldN = &mt1
return nil
}
var mt2 MyType2
if err := json.Unmarshal(m["field_n"], &mt2); err == nil {
s.FieldN = &mt2
return nil
}
return nil
}
The idea of this approach is to first unmarshal all "static" values and then deal with the interface type. There are at least 2 problems, however, with it, in my opinion:
-
In my case, the number of fields might grow in the future and the code will get even more repetitive than it currently is
-
Even the current version requires checking that the map
m
has keyfield_i
otherwise I would just getunexpected end of input
. This is even more cumbersome.
Is there a more elegant way to do the following:
- Unmarshal all fields with static types
- Handle the only special interface-typed value
Thanks!
Important update:
It should be noted that Field1
effectively defines which concrete type should be used for FieldN
. This, as was noted in the comments, should simplify the approach considerably but I still struggle a bit with the correct implementation.
答案1
得分: 5
使用json.RawMessage来捕获对象的可变部分。使用应用程序逻辑确定的类型解码原始消息。
func (m *MyObj) UnmarshalJSON(data []byte) error {
// 声明一个与MyObj具有相同字段但没有方法的新类型。
// 当解组下面声明的类型Y的值时,使用此类型避免递归。
type X MyObj
// 声明一个类型,将field_n作为原始消息捕获,
// 并将所有其他字段作为普通字段。
// MyObj中的FieldN被此处的FieldN遮蔽。
type Y struct {
*X
FieldN json.RawMessage `json:"field_n"`
}
// 将field_n解组为原始消息,将所有其他字段解组为m。
y := Y{X: (*X)(m)}
err := json.Unmarshal(data, &y)
if err != nil {
return err
}
// 现在,我们在y.FieldN中有一个json.RawMessage作为field_n。
// 我们可以使用任何逻辑来确定具体类型,创建该类型的值,并对其进行解组。
//
// 这里,我假设field_1指定了具体类型。
switch m.Field1 {
case "type1":
m.FieldN = &MyType1{}
case "type2":
m.FieldN = &MyType2{}
default:
return errors.New("unknown field 1")
}
return json.Unmarshal(y.FieldN, m.FieldN)
}
链接:https://go.dev/play/p/hV3Lgn1RkBz
英文:
Use json.RawMessage to capture the varying part of the object. Decode the raw message using type determined in application logic.
func (m *MyObj) UnmarshalJSON(data []byte) error {
// Declare new type with same fields as MyObj, but
// but no methods. This type is used to avoid
// recursion when unmarshaling a value of type
// Y declared below.
type X MyObj
// Declare a type to capture field_n as a raw message
// and all other fields as normal. The FieldN in
// MyObj is shadowed by the FieldN here.
type Y struct {
*X
FieldN json.RawMessage `json:"field_n"`
}
// Unmarshal field_n to the raw message and all other fields
// to m.
y := Y{X: (*X)(m)}
err := json.Unmarshal(data, &y)
if err != nil {
return err
}
// We now have field_n as a json.RawMessage in y.FieldN.
// We can use whatever logic we want to determine the
// concrete type, create a value of that type, and unmarshal
// to that value.
//
// Here, I assume that field_1 specifies the concrete type.
switch m.Field1 {
case "type1":
m.FieldN = &MyType1{}
case "type2":
m.FieldN = &MyType2{}
default:
return errors.New("unknown field 1")
}
return json.Unmarshal(y.FieldN, m.FieldN)
}
答案2
得分: 1
这个演示是基于@mkopriva的建议(DisallowUnknownFields
),但仍然使用“尝试一个;如果失败,则尝试另一个”的过程。
package main
import (
"bytes"
"encoding/json"
"fmt"
)
type MyObj struct {
Field1 string `json:"field_1"`
FieldN MyInterface `json:"field_n"`
}
type MyInterface interface{}
type MyType1 struct {
FF1 string `json:"ff1"`
}
type MyType2 struct {
FF2 string `json:"ff2"`
}
func (m *MyObj) UnmarshalJSON(data []byte) error {
// 我们不能直接使用MyObj。如果这样做,json解码器将调用此函数,并导致堆栈溢出恐慌。
// 将“type MyObj1 MyObj”替换为“type MyObj1 = MyObj”,您将看到错误。
type MyObj1 MyObj
out := MyObj1{FieldN: &MyType1{}}
dec := json.NewDecoder(bytes.NewReader(data))
dec.DisallowUnknownFields()
if err := dec.Decode(&out); err == nil {
*m = MyObj(out)
return nil
}
out.FieldN = &MyType2{}
dec = json.NewDecoder(bytes.NewReader(data))
dec.DisallowUnknownFields()
if err := dec.Decode(&out); err == nil {
*m = MyObj(out)
return nil
} else {
return err
}
}
func main() {
test(`{"field_1":"field1","field_n":{"ff1":"abc"}}`)
test(`{"field_1":"field1","field_n":{"ff2":"abc"}}`)
}
func test(input string) {
var obj MyObj
if err := json.Unmarshal([]byte(input), &obj); err != nil {
fmt.Println(err)
} else {
fmt.Printf("%#v, %#v\n", obj, obj.FieldN)
}
}
输出结果:
main.MyObj{Field1:"field1", FieldN:(*main.MyType1)(0xc00009e270)}, &main.MyType1{FF1:"abc"}
main.MyObj{Field1:"field1", FieldN:(*main.MyType2)(0xc00009e3a0)}, &main.MyType2{FF2:"abc"}
英文:
This demo is based on @mkopriva's suggestion (DisallowUnknownFields
) but still use the "try one; if failed, try another"
procedure.
package main
import (
"bytes"
"encoding/json"
"fmt"
)
type MyObj struct {
Field1 string `json:"field_1"`
FieldN MyInterface `json:"field_n"`
}
type MyInterface interface{}
type MyType1 struct {
FF1 string `json:"ff1"`
}
type MyType2 struct {
FF2 string `json:"ff2"`
}
func (m *MyObj) UnmarshalJSON(data []byte) error {
// We can not use MyObj directly. If we do this, the json decoder will
// call this func, and result in a stack overflow panic. replace
// "type MyObj1 MyObj" with "type MyObj1 = MyObj" and you will see the error.
type MyObj1 MyObj
out := MyObj1{FieldN: &MyType1{}}
dec := json.NewDecoder(bytes.NewReader(data))
dec.DisallowUnknownFields()
if err := dec.Decode(&out); err == nil {
*m = MyObj(out)
return nil
}
out.FieldN = &MyType2{}
dec = json.NewDecoder(bytes.NewReader(data))
dec.DisallowUnknownFields()
if err := dec.Decode(&out); err == nil {
*m = MyObj(out)
return nil
} else {
return err
}
}
func main() {
test(`{"field_1":"field1","field_n":{"ff1":"abc"}}`)
test(`{"field_1":"field1","field_n":{"ff2":"abc"}}`)
}
func test(input string) {
var obj MyObj
if err := json.Unmarshal([]byte(input), &obj); err != nil {
fmt.Println(err)
} else {
fmt.Printf("%#v, %#v\n", obj, obj.FieldN)
}
}
The output:
main.MyObj{Field1:"field1", FieldN:(*main.MyType1)(0xc00009e270)}, &main.MyType1{FF1:"abc"}
main.MyObj{Field1:"field1", FieldN:(*main.MyType2)(0xc00009e3a0)}, &main.MyType2{FF2:"abc"}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论