英文:
How to specify default values when parsing JSON in Go
问题
我想在Go语言中解析一个JSON对象,但是希望为未给定的字段指定默认值。例如,我有以下结构体类型:
type Test struct {
    A string
    B string
    C string
}
A、B和C的默认值分别为"a"、"b"和"c"。这意味着当我解析以下JSON时:
{"A": "1", "C": 3}
我希望得到以下结构体:
Test{A: "1", B: "b", C: "3"}
是否可以使用内置的encoding/json包实现这个功能?否则,是否有任何具有此功能的Go库可用?
英文:
I want to parse a JSON object in Go, but want to specify default values for fields that are not given. For example, I have the struct type:
type Test struct {
	A string
	B string
	C string
}
The default values for A, B, and C, are "a", "b", and "c" respectively. This means that when I parse the json:
{"A": "1", "C": 3}
I want to get the struct:
Test{A: "1", B: "b", C: "3"}
Is this possible using the built-in package encoding/json? Otherwise, is there any Go library that has this functionality?
答案1
得分: 92
这可以通过使用encoding/json实现:在调用json.Unmarshal时,您不需要给它一个空结构体,可以给它一个带有默认值的结构体。
对于您的示例:
var example []byte = []byte(`{"A": "1", "C": "3"}`)
out := Test{
    A: "default a",
    B: "default b",
    // C的默认值将是"",即字符串的空值
}
err := json.Unmarshal(example, &out) // <--
if err != nil {
    panic(err)
}
fmt.Printf("%+v", out)
运行此示例将返回{A:1 B:default b C:3}。
正如您所看到的,json.Unmarshal(example, &out)将JSON解组为out,覆盖了JSON中指定的值,但保持其他字段不变。
英文:
This is possible using encoding/json: when calling json.Unmarshal, you do not need to give it an empty struct, you can give it one with default values.
For your example:
var example []byte = []byte(`{"A": "1", "C": "3"}`)
out := Test{
	A: "default a",
	B: "default b",
	// default for C will be "", the empty value for a string
}
err := json.Unmarshal(example, &out) // <--
if err != nil {
	panic(err)
}
fmt.Printf("%+v", out)
Running this example returns {A:1 B:default b C:3}.
As you can see, json.Unmarshal(example, &out) unmarshals the JSON into out, overwriting the values specified in the JSON, but leaving the other fields unchanged.
答案2
得分: 27
如果您有一个Test结构体的列表或映射,那么接受的答案就不再适用了,但可以通过添加一个UnmarshalJSON方法来轻松扩展它:
func (t *Test) UnmarshalJSON(data []byte) error {
    type testAlias Test
    test := &testAlias{
        B: "default B",
    }
    err := json.Unmarshal(data, test)
    if err != nil {
        return err
    }
    *t = Test(*test)
    return nil
}
testAlias是为了防止对UnmarshalJSON的递归调用。这是因为新类型没有定义方法。
链接:https://play.golang.org/p/qiGyjRbNHg
英文:
In case u have a list or map of Test structs the accepted answer is not possible anymore but it can easily be extended with a UnmarshalJSON method:
func (t *Test) UnmarshalJSON(data []byte) error {
  type testAlias Test
  test := &testAlias{
    B: "default B",
  }
  err := json.Unmarshal(data, test)
  if err != nil {
      return err
  }
  *t = Test(*test)
  return nil
}
The testAlias is needed to prevent recursive calls to UnmarshalJSON. This works because a new type has no methods defined.
答案3
得分: 4
你也可以实现UnmarshalJSON接口并在其中设置默认值。
func (test *Test) UnmarshalJSON(data []byte) error {
    test.A = "1"
    test.B = "2"
    test.C = "3"
    type tempTest Test
    return json.Unmarshal(data, (*tempTest)(test))
}
需要使用tempTest类型来避免递归调用UnmarshalJSON。
英文:
You can also implement the UnmarshalJSON interface and set the default values in it.
func (test *Test) UnmarshalJSON(data []byte) error {
    test.A = "1"
    test.B = "2"
    test.C = "3"
    type tempTest Test
    return json.Unmarshal(data, (*tempTest)(test))
}
The type tempTest is needed so that UnmarshalJSON is not called recursively.
答案4
得分: 1
json.Unmarshal与默认值一起使用简单而干净,就像Christian和JW给出的答案一样,但它也有一些缺点。
- 首先,它将字段的默认值与解析逻辑紧密地联系在一起。可以想象,我们希望让用户代码在后续设置其默认值;但现在,默认值必须在解组之前设置。
 - 第二个缺点是它只适用于简单的情况。如果我们的Options结构体具有其他结构体的切片或映射,我们无法以这种方式填充默认值。
 
另一个选项是使用指针字段的默认值
	type Test struct {
		A *string
		B *string
		C *string
	}
	js := []byte(`{"A": "1", "C": "3"}`)
	var t Test
	if err := json.Unmarshal(js, &t); err != nil {
		fmt.Println(err)
	}
	if t.B == nil {
		var defaultB = "B"
		t.B = &defaultB
	}
英文:
The json.Unmarshal with a default value is simple and clean like the answers given by Christian and JW., but it has some downsides.
- First, it strongly ties the default values of fields with the parsing logic. It's conceivable that we want to let user code down the line set its defaults; right now, the defaults have to be set before unmarshaling.
 - The second downside is that it only works in simple cases. If our Options struct has a slice or map of other structs, we can't populate defaults this way.
 
Another option is Default values with pointer fields
	type Test struct {
		A *string
		B *string
		C *string
	}
	js := []byte(`{"A": "1", "C": "3"}`)
	var t Test
	if err := json.Unmarshal(js, &t); err != nil {
		fmt.Println(err)
	}
	if t.B == nil {
		var defaultB = "B"
		t.B = &defaultB
	}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论