英文:
Go: Unmarshalling JSON with multiple types
问题
我遇到了一个问题,无法将JSON响应解组为结构体。问题是邮政编码可以返回为字符串或整数。我该如何编写一个解组方法来检查邮政编码是否为整数,并强制将其存储为字符串?
结构体:
type CustomerAddress struct {
    Line1            string `json:"line1"`
    City             string `json:"city"`
    State            string `json:"state"`
    Zip              string `json:"zip"`
    IsPrimaryAddress string `json:"isPrimaryAddress"`
}
示例JSON:
"address": [
  {
    "line1": "555 ADDRESS PLACE",
    "city": "DALLAS",
    "state": "TX",
    "isPrimaryAddress": "Y",
    "zip": 55555
  }
]
解组后,结果应该成功将邮政编码转换为字符串:
"address": [
  {
    "line1": "555 ADDRESS PLACE",
    "city": "DALLAS",
    "state": "TX",
    "isPrimaryAddress": "Y",
    "zip": "55555"
  }
]
作为一种尝试,我尝试使用了一个ZipWrapper。
type CustomerAddress struct {
    Line1            string     `json:"line1"`
    City             string     `json:"city"`
    State            string     `json:"state"`
    Zip              ZipWrapper `json:"zip"`
    IsPrimaryAddress string     `json:"isPrimaryAddress"`
}
type ZipWrapper struct {
    Zip string
}
func (w *ZipWrapper) UnmarshalJSON(data []byte) (err error) {
    if zip, err := strconv.Atoi(string(data)); err == nil {
        w.Zip = strconv.Itoa(zip)
        return nil
    }
    return json.Unmarshal(data, &w.Zip)
}
这个方法几乎可以工作,但是邮政编码现在是CustomerAddress中的一个嵌套结构,这不是我想要的:
"address": [
  {
    "line1": "555 ADDRESS PLACE",
    "city": "DALLAS",
    "state": "TX",
    "isPrimaryAddress": "Y",
    "zip": {
      "Zip": "55555"
    }
  }
]
有什么想法吗?我觉得这是一个相对简单的任务,但我是一个完全的Go新手,还没有完全理解解组的工作原理。
英文:
I'm having an issue unmarshalling a JSON response into a struct. The problem I'm having is that the zip code can either return as a string or an integer. How do I write an unmarshal method to check if the zip is an int and force it to store it as a string?
Struct:
type CustomerAddress struct {
	Line1            string `json:"line1"`
	City             string `json:"city"`
	State            string `json:"state"`
	Zip 			 string `json:"zip"`
	IsPrimaryAddress string `json:"isPrimaryAddress"`
}
Example Json:
address": [
  {
    "line1": "555 ADDRESS PLACE",
    "city": "DALLAS",
    "state": "TX",
    "isPrimaryAddress": "Y",
    "zip": 55555
  }
]
After unmarshalling, the result should have the zip successfully converted into a string:
address": [
  {
    "line1": "555 ADDRESS PLACE",
    "city": "DALLAS",
    "state": "TX",
    "isPrimaryAddress": "Y",
    "zip": "55555"
  }
]
As an attempt, I tried to use a ZipWrapper.
type CustomerAddress struct {
    Line1            string        `json:"line1"`
    City             string        `json:"city"`
    State            string        `json:"state"`
    Zip              ZipWrapper    `json:"zip"`
    IsPrimaryAddress string        `json:"isPrimaryAddress"`
}
type ZipWrapper struct {
   Zip string
}
func (w *ZipWrapper ) UnmarshalJSON(data []byte) (err error) {
    if zip, err := strconv.Atoi(string(data)); err == nil {
	    w.Zip = strconv.Itoa(zip)
	    return nil
    }
    return json.Unmarshal(data, &w.Zip)
}
This almost worked except the zip is now a nested struct within CustomerAddress which is not what I want:
  address": [
  {
    "line1": "555 ADDRESS PLACE",
    "city": "DALLAS",
    "state": "TX",
    "isPrimaryAddress": "Y",
    "zip": {
      "Zip": "55555"
    }
  }
]
Any ideas? I feel like this is a relatively easy task but I'm a complete Go noob and haven't fully wrapped my head around how Unmarshalling works.
答案1
得分: 12
json包提供了json.Number类型来实现这个功能:
type CustomerAddress struct {
    Line1            string      `json:"line1"`
    City             string      `json:"city"`
    State            string      `json:"state"`
    Zip              json.Number `json:"zip"`
    IsPrimaryAddress string      `json:"isPrimaryAddress"`
}
如果你需要在没有嵌套结构的情况下自己实现,你可以像json.Number一样声明类型,使用string作为底层类型:
type ZipWrapper string
func (w *ZipWrapper) UnmarshalJSON(data []byte) (err error) {
    if len(data) > 1 && data[0] == '"' && data[len(data)-1] == '"' {
        data = data[1 : len(data)-1]
    }
    if _, err := strconv.Atoi(string(data)); err != nil {
        return err
    }
    *w = ZipWrapper(string(data))
    return nil
}
链接:https://play.golang.org/p/PIKSh2c6Mm
英文:
The json package provides the json.Number type to do this:
type CustomerAddress struct {
	Line1            string      `json:"line1"`
	City             string      `json:"city"`
	State            string      `json:"state"`
	Zip              json.Number `json:"zip"`
	IsPrimaryAddress string      `json:"isPrimaryAddress"`
}
https://play.golang.org/p/PIKSh2c6Mm
If you needed to do this yourself without the nested struct, you can declare the type the same way as json.Number, with string as the underlying type
type ZipWrapper string
func (w *ZipWrapper) UnmarshalJSON(data []byte) (err error) {
	if len(data) > 1 && data[0] == '"' && data[len(data)-1] == '"' {
		data = data[1 : len(data)-1]
	}
	if _, err := strconv.Atoi(string(data)); err != nil {
		return err
	}
	*w = ZipWrapper(string(data))
	return nil
}
答案2
得分: 2
Jim在另一个答案中所说的关于将ZipWrapper定义为字符串的意思是,你可以采用与之前相同的方法,但不需要嵌套的结构体。
例如,将字段定义为:
Zip ZipWrapper `json:"zip"`
然后将ZipWrapper定义为:
type ZipWrapper string
你的UnmarshalJSON函数可以像这样:
func (w *ZipWrapper) UnmarshalJSON(data []byte) (err error) {
    if zip, err := strconv.Atoi(string(data)); err == nil {
        str := strconv.Itoa(zip)
        *w = ZipWrapper(str)
        return nil
    }
    var str string
    err = json.Unmarshal(data, &str)
    if err != nil {
       return err
    }
    return json.Unmarshal([]byte(str), w)
}
这是一个可工作的Go playground示例:
https://play.golang.org/p/IlJJRP3x1w
英文:
What Jim is saying in the other answer about defining ZipWrapper as a string is that you can take the same approach you were taking, but without the nested Struct.
Like, define the field like this:
Zip ZipWrapper `json:"zip"`
But then ZipWrapper is defined like:
type ZipWrapper string
Your UnmarshalJSON function can be like this:
func (w *ZipWrapper) UnmarshalJSON(data []byte) (err error) {
    if zip, err := strconv.Atoi(string(data)); err == nil {
        str := strconv.Itoa(zip)
        *w = ZipWrapper(str)
        return nil
    }
    var str string
    err = json.Unmarshal(data, &str)
    if err != nil {
       return err
    }
    return json.Unmarshal([]byte(str), w)
}
Here's a working Go playground:
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论