自定义的 JSON 编组器,可以是字符串或者是 map[string]string / map[string]bool。

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

Custom Marshaler for JSON that can be string or a map[string]string / map[string]bool

问题

我正在使用一个JSON响应进行工作,有时返回一个字符串,有时返回一个具有字符串键但值为字符串和布尔值的对象。我理解我需要为数据实现自己的Unmarshaler。

示例JSON情况:

caseOne := `"data": [
    {"user": "usersName"}
]`

caseTwo := `"data": [
    {"user": {"id": "usersId", "isActive": true}}
]`

我的代码:

package main

type Result struct {
    Data []Item `json:"data"`
}

type Item struct {
    User User `json:"user"`
}

type User struct {
    user string
}

func (u *User) MarshalJSON() ([]byte, error) {
    return json.Marshal(u.user)
}

func (u *User) UnmarshalJSON(data []byte) error {
    var raw interface{}
    json.Unmarshal(data, &raw)

    switch raw := raw.(type) {
    case string:
        *u = User{raw}
    case map[string]interface{}:
        // 如何处理值类型不同的其他 "isActive" 键?
        *u = User{raw["id"].(string)}
    }
    return nil
}

这个问题/答案:这里 接近回答了我的用例,但我在如何处理多个不同值类型的映射值上有些困惑。

当前的Go Playground:这里

英文:

I am working with a JSON response that can sometimes return a string or an object with string keys but values that are string and bool. I understand I need to implement my own Unmarshaler for the data

Example JSON Situations:

caseOne := `"data": [
    {"user": "usersName"}
]`

caseTwo := `"data": [
    {"user": {"id": "usersId", "isActive": true}}
]`

My Code:

package main

type Result struct {
    Data []Item `json:"data"`
}

type Item struct {
    User User `json:"user"`
}

type User struct {
    user string
}

func (u *User) MarshalJSON() ([]byte, error) {
    return json.Marshal(u.user)
}

func (u *User) UnmarshalJSON(data []byte) error {
    var raw interface{}
    json.Unmarshal(data, &raw)

    switch raw := raw.(type) {
    case string:
        *u = User{raw}
    case map[string]interface{}:
        // how do I handle the other "isActive" key that is map[string]bool?
        *u = User{raw["id"].(string)}
    }
    return nil

}

This question/answer: Here comes close to answering my use case but I am a bit stuck on how to handle multiple map values of different value types.

Current Go Playground:
Here

答案1

得分: 3

type User struct {
	Id       string `json:"id"`
	Name     string `json:"name"`
	IsActive bool   `json:"isActive"`
}

func (u User) MarshalJSON() ([]byte, error) {
	if u == (User{Name: u.Name}) { // 检查u是否只包含name字段
		return json.Marshal(u.Name)
	}
	type U User
	return json.Marshal(U(u))
}

func (u *User) UnmarshalJSON(data []byte) error {
	switch data[0] {
	case '"': // 字符串?
		return json.Unmarshal(data, &u.Name)
	case '{': // 对象?
		type U User
		return json.Unmarshal(data, (*U)(u))
	}

	return fmt.Errorf("不支持的JSON格式:%s", string(data))
}

如果你在MarshalJSON方法内部直接将u传递给json.Marshal,或者在UnmarshalJSON方法内部直接将u传递给json.Unmarshal,你的程序将陷入无限递归,并最终导致堆栈溢出,因为MarshalJSON/UnmarshalJSON方法会被json.Marshal/json.Unmarshal自动调用,而这些方法又会调用json.Marshal/json.Unmarshal,从而形成无限循环。

为了避免这个问题,使用了类型U

语句type U User声明了一个新类型U,其底层类型与User相同。由于底层类型相同,我们可以轻松地将一个类型转换为另一个类型,反之亦然。然而,类型声明语句并不会将旧类型的方法“继承”到新类型上,因此新类型U不具有之前在User上声明的任何方法,因此json.Marshal/json.Unmarshal不会再陷入无限调用递归。

英文:
type User struct {
	Id       string `json:"id"`
	Name     string `json:"name"`
	IsActive bool   `json:"isActive"`
}

func (u User) MarshalJSON() ([]byte, error) {
	if u == (User{Name: u.Name}) { // check if u contains only name
		return json.Marshal(u.Name)
	}
	type U User
	return json.Marshal(U(u))
}

func (u *User) UnmarshalJSON(data []byte) error {
	switch data[0] {
	case '"': // string?
		return json.Unmarshal(data, &u.Name)
	case '{': // object?
		type U User
		return json.Unmarshal(data, (*U)(u))
	}

	return fmt.Errorf("unsupported JSON: %s", string(data))
}

https://go.dev/play/p/toOIz0XOQUo


If you pass u directly to json.Marshal from inside MarshalJSON, or if you pass it directly to json.Unmarshal from inside UnmarshalJSON, your program will get stuck in infinite recursion and eventually overflow the stack since MarshalJSON/UnmarshalJSON are called automatically by json.Marshal/json.Unmarshal on any value that implements those methods.

Type U is used to avoid this problem.

The statement type U User declares a new type U with its underlying type identical to that of User. Because the underlying types are identical we can easily convert one type to the other and back. However, the type declaration statement does not "carry over" the methods from the old type to the new type, so the new type U has none of the methods previously declared on User and therefore json.Marshal/json.Unmarshal will not get stuck in infinite call recursion anymore.

huangapple
  • 本文由 发表于 2023年4月14日 00:51:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/76007977.html
匿名

发表评论

匿名网友

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

确定