Go:使用多种类型进行JSON反序列化

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

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:

https://play.golang.org/p/IlJJRP3x1w

huangapple
  • 本文由 发表于 2017年5月19日 05:20:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/44057958.html
匿名

发表评论

匿名网友

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

确定