Encoding/Decoding multi-type fields in JSON golang

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

Encoding/Decoding multi-type fields in JSON golang

问题

我正在尝试创建一个结构体,其中一个字段可以保存几种特定类型的数据,比如intstringCustomType。我想要将这个结构体与JSON进行解码/编码。在Go语言中,我们如何实现这个功能呢?

例如,我有一个如下定义的结构体:

type MyData struct {
  Name  string                                    `json:"name"`
  Value int32                                     `json:"value"`
  Param <可以是intstring或CustomType> `json:"param"`
}

其中CustomType定义如下:

type CustomType struct {
  Custom bool `json:"custom"`
}

假设我需要将以下JSON解组到上述MyData结构体中:

{
  "name": "Hello",
  "value": 32,
  "param": "World"
}

还有这个:

{
  "name": "Hello",
  "value": 32,
  "param": 100
}

以及这个:

{
  "name": "Hello",
  "value": 32,
  "param": {
    "custom": true
  }
}

我该如何实现这个功能呢?

我可以在MyData上定义自己的MarshalJSONUnmarshalJSON方法来实现吗?

或者是否有一种定义自定义类型(比如IntOrStringOrCustom)并在MyData中使用IntOrStringOrCustom字段的方法,然后在IntOrStringOrCustom上定义MarshalJSONUnmarshalJSON方法的方式呢?

我还看到了json.RawMessage,我们能在这里使用它吗?

使用interface{}的问题是,我将不得不在每个使用这些数据的地方编写编码/解码逻辑。是否有一种优雅的方式可以使用interface{}来解决这个问题?

英文:

I am trying to create a struct where a field can hold data of a few particular types, say int, string and a CustomType. I want to decode/encode this struct to/from JSON. How can we achieve this in go/golang?

For example, I have a struct for the following definition:

type MyData struct {
  Name  string                                    `json:&quot;name&quot;`
  Value int32                                     `json:&quot;value&quot;`
  Param &lt;can be either int, string or CustomType&gt; `json:&quot;param&quot;`
}

Where CustomType is

type CustomType struct {
  Custom bool `json:&quot;custom&quot;`
}

Let's say I need to unmarshal the following JSONs to the above struct MyData:

{
  &quot;name&quot;: &quot;Hello&quot;,
  &quot;value&quot;: 32
  &quot;param&quot;: &quot;World&quot;
}

And this one:

{
  &quot;name&quot;: &quot;Hello&quot;,
  &quot;value&quot;: 32
  &quot;param&quot;: 100
}

And this one also:

{
  &quot;name&quot;: &quot;Hello&quot;,
  &quot;value&quot;: 32
  &quot;param&quot;: {
     &quot;custom&quot;: true
  }
}

How do I achieve this?

Can I define my own MarshalJSON and UnmarshalJSON on MyData and achieve this?

Or is there a way of defining a custom type, say IntOrStringOrCustom and define MyData as

type MyData struct {
  Name  string              `json:&quot;name&quot;`
  Value int32               `json:&quot;value&quot;`
  Param IntOrStringOrCustom `json:&quot;param&quot;`
}

and then define MarshalJSON and UnmarshalJSON on IntOrStringOrCustom?

I have also seen json.RawMessage. Can we use it somehow here?

Issue with using interface{} is that I will have to write the encoding/decoding logic everywhere, where I am trying to use this data. Or is there an elegant way of doing this with interface{}?

答案1

得分: 4

更新。interface可以自动编码和解码为JSON。如果你希望控制类型,可以添加特殊的UnmarshalJSON方法并在其中进行检查:

type TheParam interface{}

type MyData struct {
    Name  string   `json:"name"`
    Value int32    `json:"value"`
    Param TheParam `json:"param"`
}

type myData MyData

func (m *MyData) UnmarshalJSON(b []byte) error {
    var mm myData
    if err := json.Unmarshal(b, &mm); err != nil {
        return err
    }
    switch mm.Param.(type) {
    case float64, string, map[string]interface{}:
        *m = MyData(mm)
        return nil
    default:
        return InvalidFieldTypeError{value: mm.Param}
    }
    return nil
}

type InvalidFieldTypeError struct {
    value interface{}
}

func (e InvalidFieldTypeError) Error() string {
    return fmt.Sprintf("Field type '%T' is not valid for MyData", e.value)
}

InvalidFieldTypeError类型可以方便地返回此类错误,并可以定义为:

type InvalidFieldTypeError struct {
    value interface{}
}

func (e InvalidFieldTypeError) Error() string {
    return fmt.Sprintf("Field type '%T' is not valid for MyData", e.value)
}

完整示例:https://play.golang.org/p/MuW6gwSAKi

另外,我想推荐这篇文章:https://attilaolah.eu/2013/11/29/json-decoding-in-go/

英文:

UPDATED. interface gets encoded and decoded to JSON well automatically. If you wish to control types, you may add special UnmarshalJSON and perform checks in it:

type TheParam interface{}

type MyData struct {
	Name  string   `json:&quot;name&quot;`
	Value int32    `json:&quot;value&quot;`
	Param TheParam `json:&quot;param&quot;`
}

type myData MyData

func (m *MyData) UnmarshalJSON(b []byte) error {
	var mm myData
	if err := json.Unmarshal(b, &amp;mm); err != nil {
		return err
	}
	switch mm.Param.(type) {
	case float64, string, map[string]interface{}:
		*m = MyData(mm)
		return nil
	default:
		return InvalidFieldTypeError{value: mm.Param}
	}
	return nil
}

Type InvalidFieldTypeError may be convenient to return such class of errors and can be defined as:

type InvalidFieldTypeError struct {
	value interface{}
}

func (e InvalidFieldTypeError) Error() string {
	return fmt.Sprintf(&quot;Field type &#39;%T&#39; is not valid for MyData&quot;, e.value)
}

The whole example: https://play.golang.org/p/MuW6gwSAKi

Also I'd like to recommend this article https://attilaolah.eu/2013/11/29/json-decoding-in-go/

huangapple
  • 本文由 发表于 2017年9月18日 20:15:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/46279048.html
匿名

发表评论

匿名网友

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

确定