在Go中解组顶层的JSON数组

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

Unmarshalling top-level JSON array in Go

问题

我正在通过编写一个简单的http服务器来学习Go语言,并且我需要处理一些JSON响应。

对于对象响应,我可以用两行代码按惯例进行解组:
structResult := Foo{}
json.Unmarshal(structBody, &structResult)

但是我不知道如何处理数组响应(请参见下面的示例)。是否有一种方法可以指定(可能通过json标签)将顶级数组放入给定的结构字段中?

package main

import "fmt"
import "encoding/json"

type Foo struct {
    Id   uint64 `json:"id"`
    Name string `json:"name"`
}

type BaseResult struct {
    Error string `json:"error"`
}

type FooResult struct {
    BaseResult
    Foos []Foo
}

func main() {
    // 简单且有效。
    structBody := []byte(`{"id": 1,"name": "foo"}`)
    structResult := Foo{}
    json.Unmarshal(structBody, &structResult)
    fmt.Printf("%#v\n", structResult)
    
    // 不起作用。
    arrayBody := []byte(`[{"id": 1,"name": "foo"},{"id": 2,"name": "bar"},{"id": 3,"name": "foobar"}]`)    
    arrayResult := FooResult{}
    json.Unmarshal(arrayBody, &arrayResult)
    fmt.Printf("%#v\n", arrayResult)
}

我知道我可以将FooResult定义为一个数组:

type FooResult []Foo

但是这样我就失去了指定基本对象的能力,而我希望使用它来存储错误消息等。我也知道我可以直接解组到&fooResult.Foos,但是我希望代码能够同时适用于对象和数组。

更新

按照@dyoo的建议实现UnmarshalJSON部分解决了我的问题,但是我希望在JSON具有不同结构的情况下,可以使用BaseResult来存储解析错误:

arrayBody := []byte(`{"error": "foo"}`)
arrayResult := FooResult{}
json.Unmarshal(arrayBody, &arrayResult)
fmt.Printf("%#v\n", arrayResult)

当然,我可以在UnmarshalJSON中实现更复杂的逻辑,但是难道没有更简单的方法吗?

英文:

I'm learning Go by writing a simple http server and I need to handle some JSON responses.

With an object response, I can unmarshal it idiomatically with 2 lines of code:
structResult := Foo{}
json.Unmarshal(structBody, &structResult)

I don't know how to do the same for an array response (see the example below). Is there a way to specify (possibly via json tag) that top-level array should go into a given struct field?

package main

import "fmt"
import "encoding/json"

type Foo struct {
	Id uint64 `json:"id"`
	Name string `json:"name"`
}

type BaseResult struct {
	Error string  `json:"error"`
}

type FooResult struct {
	BaseResult
	Foos []Foo
}

func main() {
    // Simple and works.
	structBody := []byte(`{"id": 1,"name": "foo"}`)
	structResult := Foo{}
	json.Unmarshal(structBody, &structResult)
	fmt.Printf("%#v\n", structResult)
	
    // Doesn't work.
	arrayBody := []byte(`[{"id": 1,"name": "foo"},{"id": 2,"name": "bar"},{"id": 3,"name": "foobar"}]`)	
	arrayResult := FooResult{}
	json.Unmarshal(arrayBody, &arrayResult)
	fmt.Printf("%#v\n", arrayResult)
}

I know I could make FooResult an array:

type FooResult []Foo

but then I lose the ability to specify base object which I would like to use to store error message and such. I also know that I can unmarshal into &fooResult.Foos directly, but I want the code to work with both objects and arrays.

UPDATE

Implementing UnmarshalJSON as suggested by @dyoo partially solves my problem, but I was hoping that I could use BaseResult to store parse error in case JSON has a different structure:

arrayBody := []byte(`{"error": "foo"}`)
arrayResult := FooResult{}
json.Unmarshal(arrayBody, &arrayResult)
fmt.Printf("%#v\n", arrayResult)

Of course I could implement more complex logic inside UnmarshalJSON - but isn't there a simpler way to do it?

答案1

得分: 2

你可以在FooResult中实现json.Unmarshaler接口,以自定义其在解组时的行为。(类似地,还有一个json.Marshaler接口。)

添加以下代码:

func (f *FooResult) UnmarshalJSON(bs []byte) error {
    return json.Unmarshal(bs, &f.Foos)
}

在此之后,你的代码应该可以正常工作。你可以在这里查看一个示例:http://play.golang.org/p/oMdoB2e-rB

你可以尝试类似以下的代码:

func (f *FooResult) UnmarshalJSON(bs []byte) error {
    err1 := json.Unmarshal(bs, &f.BaseResult)
    err2 := json.Unmarshal(bs, &f.Foos)
    if err1 != nil && err2 != nil {
        // 任意选择一个错误。
        return err1
    }
    return nil
}

不过,即使这样看起来也有些可疑。处理联合类型的结果并不是json库自动处理的设计初衷。如果你的JSON具有动态类型,你需要显式编写强制转换逻辑。

参考:https://stackoverflow.com/questions/13364181/how-to-unmarshall-an-array-of-different-types-correctly 和 http://blog.golang.org/json-and-go 了解相关问题。

英文:

You can implement the json.Unmarshaler interface in your FooResult, to customize exactly how it responds to unmarshaling. (Similarly, there's a json.Marshaler interface.)

Add:

func (f *FooResult) UnmarshalJSON(bs []byte) error {
	return json.Unmarshal(bs, &f.Foos)
}

after which your code should otherwise work. http://play.golang.org/p/oMdoB2e-rB

You might try something like:

func (f *FooResult) UnmarshalJSON(bs []byte) error {
    err1 := json.Unmarshal(bs, &f.BaseResult)
	err2 := json.Unmarshal(bs, &f.Foos)
    if err1 != nil && err2 != nil {
        // Arbitrarily choose an error.
        return err1
    }
    return nil
}

although even this is beginning to look dubious. Handling union type results is not quite what the json library is designed to handle automatically for you. You'll need to explicitly code the coercion logic if your JSON has dynamic type.

See: https://stackoverflow.com/questions/13364181/how-to-unmarshall-an-array-of-different-types-correctly and http://blog.golang.org/json-and-go for related issues.

答案2

得分: 0

只需在解组时指定Foos

package main

import "fmt"
import "encoding/json"

type Foo struct {
    Id   uint64 `json:"id"`
    Name string `json:"name"`
}

type BaseResult struct {
    Error string `json:"error"`
}

type FooResult struct {
    BaseResult
    Foos []Foo
}

func main() {
    // 简单且有效。
    structBody := []byte(`{"id": 1,"name": "foo"}`)
    structResult := Foo{}
    json.Unmarshal(structBody, &structResult)
    fmt.Printf("%#v\n", structResult)

    // 不起作用。
    arrayBody := []byte(`[{"id": 1,"name": "foo"},{"id": 2,"name": "bar"},{"id": 3,"name": "foobar"}]`)
    arrayResult := FooResult{}
    if err := json.Unmarshal(arrayBody, &arrayResult.Foos); err != nil {
        arrayResult.BaseResult.Error = string(arrayBody)
    }

    fmt.Printf("%#v\n", arrayResult)
}
英文:

Just specify Foos when you Unmarshal

package main

import "fmt"
import "encoding/json"

type Foo struct {
    Id   uint64 `json:"id"`
    Name string `json:"name"`
}

type BaseResult struct {
    Error string `json:"error"`
}

type FooResult struct {
    BaseResult
    Foos []Foo
}

func main() {
    // Simple and works.
    structBody := []byte(`{"id": 1,"name": "foo"}`)
    structResult := Foo{}
    json.Unmarshal(structBody, &structResult)
    fmt.Printf("%#v\n", structResult)

    // Doesn't work.
    arrayBody := []byte(`[{"id": 1,"name": "foo"},{"id": 2,"name": "bar"},{"id": 3,"name": "foobar"}]`)
    arrayResult := FooResult{}
    if err := json.Unmarshal(arrayBody, &arrayResult.Foos); err != nil {
        arrayResult.BaseResult.Error = string(arrayBody)
    }

    fmt.Printf("%#v\n", arrayResult)
}

huangapple
  • 本文由 发表于 2014年9月25日 16:39:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/26034101.html
匿名

发表评论

匿名网友

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

确定