解组可能返回数组的 JSON?

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

Unmarshalling a JSON that may or may not return an array?

问题

我正在从第三方网站检索JSON数据(家庭用电情况),根据我从网站请求的内容,返回的JSON可能是一个数组,也可能不是。例如,如果我请求我的智能电表列表,返回的结果可能是这样的(由于大小原因,结果被截断):

  1. {"gwrcmds":{"gwrcmd":{"gcmd":"SPA_UserGetSmartMeterList","gdata":{"gip":{"version":"1"...

其中gwrcmd是一个单独的元素。

但是,如果我请求过去半小时的用电情况,返回的结果可能是这样的:

  1. {"gwrcmds":{"gwrcmd":[{"gcmd":"DeviceGetChart","gdata":{"gip":{"version":"1"...

注意,gwrcmd现在是一个数组。

在我的Go应用程序中,我有一个结构体,看起来像这样(同样被截断,因为它还有更多的子结构体和属性在"Version"下面):

  1. type Response struct {
  2. Gwrcmds struct {
  3. Gwrcmd struct {
  4. Gcmd string
  5. Gdata struct {
  6. Gip struct {
  7. Version string

如果gwrcmd是一个数组,Gwrcmd需要是[]struct { },但如果它不是,它只是一个普通的struct { }

问题是,如果JSON是一个数组,而结构体没有切片(或反之亦然),json.Unmarshal会返回一个错误。

我是否需要创建一个第二个结构体,它与第一个结构体相同(除了使用[]struct { }),或者有更好的方法来解决这个问题?我想到了使用接口的方法,但我还没有真正接触过它们,所以对它们不是100%确定。

英文:

I'm retrieving JSON from a third party website (home electricity usage), and depending on what I've requested from the site, the JSON returned may or may not be an array. For example, if I request a list of my smart meters, I get this (results truncated, due to large size):

  1. {"gwrcmds":{"gwrcmd":{"gcmd":"SPA_UserGetSmartMeterList","gdata":{"gip":{"version":"1"...

Where gwrcmd is a single element.

But if I request electricity usage for the last half hour, I get this:

  1. {"gwrcmds":{"gwrcmd":[{"gcmd":"DeviceGetChart","gdata":{"gip":{"version":"1" ...

See how gwrcmd is now an array?

Within my Go app, I have a struct that looks like this (again, truncated, as it goes on for a while. There's more sub-structs and properties beneath "Version":

  1. type Response struct {
  2. Gwrcmds struct {
  3. Gwrcmd struct {
  4. Gcmd string
  5. Gdata struct {
  6. Gip struct {
  7. Version string

If gwrcmd is an array, Gwrcmd needs to be a []struct { }, but if it's not, it's just a regular old struct { }

The problem is that json.Unmarshal just returns an error if the JSON has an array and the struct does not have a slice (or vice versa).

Would I need to create a second struct that duplicates the first one (except with a []struct { } instead), or is there a better way to do it? I thought of something with interfaces, but I haven't really touched those yet, so I'm not 100% sure on them.

答案1

得分: 10

通常,当你有一个未知类型的 JSON 值时,你会使用 json.RawMessage 来获取它,查看它,并将其正确地解组为相应的类型。下面是一个简化的示例:

  1. // 这里的 A 可以是一个对象,也可以是一个对象的 JSON 数组。
  2. type Response struct {
  3. RawAWrapper struct {
  4. RawA json.RawMessage `json:"a"`
  5. }
  6. A A `json:"-"`
  7. As []A `json:"-"`
  8. }
  9. type A struct {
  10. B string
  11. }
  12. func (r *Response) UnmarshalJSON(b []byte) error {
  13. if err := json.Unmarshal(b, &r.RawAWrapper); err != nil {
  14. return err
  15. }
  16. if r.RawAWrapper.RawA[0] == '[' {
  17. return json.Unmarshal(r.RawAWrapper.RawA, &r.As)
  18. }
  19. return json.Unmarshal(r.RawAWrapper.RawA, &r.A)
  20. }

Playground: http://play.golang.org/p/2d_OrGltDu.

然而,根据第一个字节猜测内容似乎不太可靠。通常,你的 JSON 中会有一些线索(例如与动态字段处于同一级别的 lengthtype 字段),告诉你是一个对象还是一个数组。

另请参阅:

英文:

Usually, whenever you have a JSON value of unknown type, you will use json.RawMessage to get it, peek into it, and unmarshal it correctly into the corresponding type. A simplified example:

  1. // The A here can be either an object, or a JSON array of objects.
  2. type Response struct {
  3. RawAWrapper struct {
  4. RawA json.RawMessage `json:"a"`
  5. }
  6. A A `json:"-"`
  7. As []A `json:"-"`
  8. }
  9. type A struct {
  10. B string
  11. }
  12. func (r *Response) UnmarshalJSON(b []byte) error {
  13. if err := json.Unmarshal(b, &r.RawAWrapper); err != nil {
  14. return err
  15. }
  16. if r.RawAWrapper.RawA[0] == '[' {
  17. return json.Unmarshal(r.RawAWrapper.RawA, &r.As)
  18. }
  19. return json.Unmarshal(r.RawAWrapper.RawA, &r.A)
  20. }

Playground: http://play.golang.org/p/2d_OrGltDu.

Guessing the content based on the first byte doesn't seem too robust to me though. Usually you'll have some sort of a clue in your JSON (like a length or type field on the same level as the dynamic one) that tells you whether you have an object or an array.

See also:

答案2

得分: 5

你可以尝试创建自定义的JSON解析方法,像这样:

  1. func (a *GwrcmCustom) UnmarshalJSON(b []byte) (err error) {
  2. g, ga := Gwrcmd{}, []Gwrcmd{}
  3. if err = json.Unmarshal(b, &g); err == nil {
  4. *a = make([]Gwrcmd, 1)
  5. (*a)[0] = Gwrcmd(g)
  6. return
  7. }
  8. if err = json.Unmarshal(b, &ga); err == nil {
  9. *a = GwrcmCustom(ga)
  10. return
  11. }
  12. return
  13. }

GwrcmCustom是一个自定义类型,是Gwrcm的切片:

  1. type GwrcmCustom []Gwrcmd

所以我们将始终得到一个切片。

你可以在Go playground上尝试这个方法。

希望这能帮到你。

英文:

You can try to make custom json unmarshal method, like

  1. func (a *GwrcmCustom) UnmarshalJSON(b []byte) (err error) {
  2. g, ga := Gwrcmd{}, []Gwrcmd{}
  3. if err = json.Unmarshal(b, &g); err == nil {
  4. *a = make([]Gwrcmd, 1)
  5. []Gwrcmd(*a)[0] = Gwrcmd(g)
  6. return
  7. }
  8. if err = json.Unmarshal(b, &ga); err == nil {
  9. *a = GwrcmCustom(ga)
  10. return
  11. }
  12. return
  13. }

GwrcmCustom is a custom type, slice of Gwrcm

  1. type GwrcmCustom []Gwrcmd

So we will get slice always

Try this on <kbd>Go playground</kbd>

I hope this will help

huangapple
  • 本文由 发表于 2015年9月2日 14:34:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/32346117.html
匿名

发表评论

匿名网友

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

确定