How can I implement UnmarshalJSON for a type derived from a scalar in Go?

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

How can I implement UnmarshalJSON for a type derived from a scalar in Go?

问题

我有一个简单的类型,在Go语言中实现了子类型整数常量与字符串之间的转换。我希望能够自动将JSON中的字符串解析为该类型的值。但是我无法实现这一点,因为UnmarshalJSON方法没有提供一种返回或修改标量值的方式。它期望一个结构体,其成员由UnmarshalJSON方法设置。对于非内置标量类型,使用",string"方法也不起作用。有没有一种方法可以正确地为派生的标量类型实现UnmarshalJSON方法?

以下是我想要的示例。我希望它打印出"Hello Ralph"四次,但实际上它打印出"Hello Bob"四次,因为PersonID没有被修改。

package main

import (
	"encoding/json"
	"fmt"
)

type PersonID int

const (
	Bob PersonID = iota
	Jane
	Ralph
	Nobody = -1
)

var nameMap = map[string]PersonID{
	"Bob":    Bob,
	"Jane":   Jane,
	"Ralph":  Ralph,
	"Nobody": Nobody,
}

var idMap = map[PersonID]string{
	Bob:    "Bob",
	Jane:   "Jane",
	Ralph:  "Ralph",
	Nobody: "Nobody",
}

func (intValue PersonID) Name() string {
	return idMap[intValue]
}

func Lookup(name string) PersonID {
	return nameMap[name]
}

func (intValue *PersonID) UnmarshalJSON(data []byte) error {
	*intValue = Lookup(string(data))
	return nil
}

type MyType struct {
	Person   PersonID `json:"person"`
	Count    int      `json:"count"`
	Greeting string   `json:"greeting"`
}

func main() {
	var m MyType
	if err := json.Unmarshal([]byte(`{"person": "Ralph", "count": 4, "greeting": "Hello"}`), &m); err != nil {
		fmt.Println(err)
	} else {
		for i := 0; i < m.Count; i++ {
			fmt.Println(m.Greeting, m.Person.Name())
		}
	}
}

请注意,我对UnmarshalJSON方法进行了一些修改,将其接收者类型更改为指针类型,并在方法内部使用指针解引用来修改值。这样就可以正确地实现对派生标量类型的JSON解析了。

英文:

I have a simple type that implements conversion of subtyped integer consts to strings and vice versa in Go. I want to be able to automatically unmarshal strings in JSON to values of this type. I can't, because UnmarshalJSON doesn't give me a way to return or modify the scalar value. It's expecting a struct, whose members are set by UnmarshalJSON. The ",string" method doesn't work either for other than builtin scalar types. Is there a way to implement UnmarshalJSON correctly for a derived scalar type?

Here's an example of what I'm after. I want it to print "Hello Ralph" four times, but it prints "Hello Bob" four times because the PersonID isn't being changed.

package main
import (
&quot;encoding/json&quot;
&quot;fmt&quot;
)
type PersonID int
const (
Bob PersonID = iota
Jane
Ralph
Nobody = -1
)
var nameMap = map[string]PersonID{
&quot;Bob&quot;:    Bob,
&quot;Jane&quot;:   Jane,
&quot;Ralph&quot;:  Ralph,
&quot;Nobody&quot;: Nobody,
}
var idMap = map[PersonID]string{
Bob:    &quot;Bob&quot;,
Jane:   &quot;Jane&quot;,
Ralph:  &quot;Ralph&quot;,
Nobody: &quot;Nobody&quot;,
}
func (intValue PersonID) Name() string {
return idMap[intValue]
}
func Lookup(name string) PersonID {
return nameMap[name]
}
func (intValue PersonID) UnmarshalJSON(data []byte) error {
// The following line is not correct
intValue = Lookup(string(data))
return nil
}
type MyType struct {
Person   PersonID `json: &quot;person&quot;`
Count    int      `json: &quot;count&quot;`
Greeting string   `json: &quot;greeting&quot;`
}
func main() {
var m MyType
if err := json.Unmarshal([]byte(`{&quot;person&quot;: &quot;Ralph&quot;, &quot;count&quot;: 4, &quot;greeting&quot;: &quot;Hello&quot;}`), &amp;m); err != nil {
fmt.Println(err)
} else {
for i := 0; i &lt; m.Count; i++ {
fmt.Println(m.Greeting, m.Person.Name())
}
}
}

答案1

得分: 5

请使用指针接收器(pointer receiver)来实现unmarshal方法。如果使用值接收器(value receiver),当方法返回时对接收器的更改将丢失。

unmarshal方法的参数是JSON文本。将JSON文本解组(unmarshal)以获取一个去除所有JSON引号的普通字符串。

func (intValue *PersonID) UnmarshalJSON(data []byte) error {
    var s string
    if err := json.Unmarshal(data, &s); err != nil {
        return err
    }
    *intValue = Lookup(s)
    return nil
}

JSON标签与示例JSON之间存在不匹配。我已将JSON更改为与标签匹配,但你也可以反过来进行更改。

if err := json.Unmarshal([]byte(`{"person": "Ralph", "count": 4, "greeting": "Hello"}`), &m); err != nil {

playground示例

英文:

Use a pointer receiver for the unmarshal method. If a value receiver is used, changes to the receiver are lost when the method returns.

The argument to the unmarshal method is JSON text. Unmarshal the JSON text to get a plain string with all JSON quoting removed.

func (intValue *PersonID) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &amp;s); err != nil {
return err
}
*intValue = Lookup(s)
return nil
}

There's a mismatch between the JSON tags an the example JSON. I changed the JSON to match the tag, but you can change it the other way.

if err := json.Unmarshal([]byte(`{&quot;person&quot;: &quot;Ralph&quot;, &quot;count&quot;: 4, &quot;greeting&quot;: &quot;Hello&quot;}`), &amp;m); err != nil {

<kbd>playground example</kbd>

答案2

得分: 0

这是基于我的评论的答案。我不确定这是否完全符合您的要求,因为您的一些问题措辞让我感到困惑,但基本思想是将解组和转换分为两个不同的步骤。首先将原始数据解组为兼容的类型,然后对另一种类型进行转换或丰富您已经拥有的类型,就像下面的示例中所示。如果您愿意,您可以在UnmarshalJSON的自定义实现中隐藏此行为,但我个人建议不要这样做。以下是我的两个原因:1)它与Go的显式冗长编码风格不一致;2)我厌恶高度混淆的包/库/语言,它们会为您执行此类操作,因为迟早会让您吃亏,并且比在几个地方添加那一行额外代码花费更多(例如,花费几个小时调试对您来说毫无意义的东西)。

type MyType struct {
    Id       PersonID
    Name     string `json:"name"`
    Count    int    `json:"count"`
    Greeting string `json:"greeting"`
}

func main() {
    var m MyType
    if err := json.Unmarshal([]byte(`{"name": "Ralph", "count": 4, "greeting": "Hello"}`), &m); err != nil {
        fmt.Println(err)
    } else {
        m.Id = Lookup(m.Name) // 注意这不是解组
        // 最好将数据保持原样,然后进行单独的转换
        for i := 0; i < m.Count; i++ {
            fmt.Println(m.Greeting, m.Person.Name())
        }
    }
}
英文:

Here's an answer based on my comment. I'm not sure this is exactly what you want to do as some of your questions wording confuses me however the basic idea is to separate unmarshalling and transformation into two different steps. First unmarshal raw data into a compatible type, after do a transformation to another type or enrich the type you already have like in the example below. You're welcome to hide this behavior in a custom implementation of UnmarshalJSON if you'd like but I would personally advise against it. Here's my two reasons; 1) it's just not consistent with Go's explicit verbose coding style 2) I despise highly obfuscated packages/libraries/languages that do stuff like this for you because sooner or later it bites you in the ass an costs you a lot more than adding that 1 line of extra code in a few places (like hours trying to debug something that makes no sense to you).

type MyType struct {
Id   PersonID
Name     string   `json: &quot;name&quot;` 
Count    int      `json: &quot;count&quot;`
Greeting string   `json: &quot;greeting&quot;`
}
func main() {
var m MyType
if err := json.Unmarshal([]byte(`{&quot;name&quot;: &quot;Ralph&quot;, &quot;count&quot;: 4, &quot;greeting&quot;: &quot;Hello&quot;}`), &amp;m); err != nil {
fmt.Println(err)
} else {
m.Id = Lookup(m.Name) // see this isn&#39;t unmarshalling
// better to take the data as is and do transformation separate
for i := 0; i &lt; m.Count; i++ {
fmt.Println(m.Greeting, m.Person.Name())
}
}
}

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

发表评论

匿名网友

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

确定