英文:
Idiomatic JSON object array -> map[string]json.RawMessage
问题
我正在尝试解析一个HTTP REST API请求的结果,根据结果的数量,它可能返回一个对象或一个对象数组。
由于我正在尝试构建一个特定REST API的包装器,所以请求是通用的,调用方式如下:
// 我正在尝试转换响应的函数原型
func (client *Client) Get(endpoint string, data map[string]string) (map[string]json.RawMessage, error)
// 函数的调用方式
client.Get("/v1/users", map[string]string{"filter.abc": "lorem ipsum"})
两个响应的示例:
[
{
"abc": "def",
"efg": 123,
"hij": [
{
"klm": "nop"
}
]
},
{
"abc": "def",
"efg": 123,
"hij": [
{
"klm": "nop"
}
]
}
]
// 响应1:包含子数组的JSON对象数组
{
"abc": "def",
"efg": 123,
"hij": [
{
"klm": "nop"
}
]
}
// 响应2:在这种情况下,只返回一个元素。
我已经成功解析了响应2,代码如下:
// [...]
byteBody = ioutil.ReadAll(res.Body)
// [...]
var body map[string]json.RawMessage
if err := json.Unmarshal(byteBody, &body); err != nil { [...] }
那么,解析这个响应的最佳方式是什么?有没有办法避免编写冗余的代码并解析两个响应?我在考虑将响应应该放入的“模型”作为附加参数传递。这样做好吗?非常感谢!
英文:
I am trying to unmarshal the results of an HTTP REST API request that, depending on the number of results, returns either an object or an array of objects.
Requests are generic because I'm trying to build a wrapper around a specific REST API and it's called like:
// Function prototype where I am trying to convert the response
func (client *Client) Get(endpoint string, data map[string]string) (map[string]json.RawMessage, error)
// The way the function is called
client.Get("/v1/users", map[string]string{"filter.abc": "lorem ipsum"})
Two examples of response:
[
{
"abc": "def",
"efg": 123
"hij": [
{
"klm": "nop"
}
]
},
{
"abc": "def",
"efg": 123
"hij": [
{
"klm": "nop"
}
]
}
]
// RESPONSE 1: Array of JSON objects that have child arrays
{
"abc": "def",
"efg": 123
"hij": [
{
"klm": "nop"
}
]
}
// RESPONSE 2: In this case, only one element was returned.
I've achieved to do this for only the response 2 with something like this:
// [...]
byteBody = ioutil.ReadAll(res.Body)
// [...]
var body map[string]json.RawMessage
if err := json.Unmarshal(byteBody, &body); err != nil { [...] }
So, what's the most idiomatic way of parsing this? Is there any way to avoid writing redundant code and parse both responses? I was thinking in giving as an additional parameter the "model" the response should be put into. Is it a good practice? Thank you so much in advance!
答案1
得分: 2
检查JSON文档中第一个非空白字节,以确定文档是数组还是对象。
func decodeArrayOrObject(doc []byte) ([]map[string]json.RawMessage, error) {
trimmedDoc := bytes.TrimSpace(doc)
switch {
case len(trimmedDoc) == 0:
return nil, errors.New("empty body")
case trimmedDoc[0] == '{':
var m map[string]json.RawMessage
err := json.Unmarshal(doc, &m)
return []map[string]json.RawMessage{m}, err
case trimmedDoc[0] == '[':
var s []map[string]json.RawMessage
err := json.Unmarshal(doc, &s)
return s, err
default:
return nil, errors.New("unexpected type")
}
}
使用reflect包创建一个可以处理任意切片类型的函数:
func decodeArrayOrObject(doc []byte, slicep interface{}) error {
trimmedDoc := bytes.TrimSpace(doc)
switch {
case len(trimmedDoc) == 0:
return errors.New("empty document")
case trimmedDoc[0] == '[':
return json.Unmarshal(doc, slicep)
case trimmedDoc[0] == '{':
s := reflect.ValueOf(slicep).Elem()
s.Set(reflect.MakeSlice(s.Type(), 1, 1))
return json.Unmarshal(doc, s.Index(0).Addr().Interface())
default:
return errors.New("unexpected type")
}
}
使用指向结构体切片或结构体指针切片的指针调用该函数:
var v []Example
err := decodeArrayOrObject(body, &v)
在playground上运行示例:点击这里。
英文:
Examine the first non-whitespace byte in the JSON document to determine if the document is an array or object.
func decodeArrayOrObject(doc []byte) ([]map[string]json.RawMessage, error) {
trimmedDoc := bytes.TrimSpace(doc)
switch {
case len(trimmedDoc) == 0:
return nil, errors.New("empty body")
case trimmedDoc[0] == '{':
var m map[string]json.RawMessage
err := json.Unmarshal(doc, &m)
return []map[string]json.RawMessage{m}, err
case trimmedDoc[0] == '[':
var s []map[string]json.RawMessage
err := json.Unmarshal(doc, &s)
return s, err
default:
return nil, errors.New("unexpected type")
}
}
Use the reflect package to create a function that works with arbitrary slice types:
func decodeArrayOrObject(doc []byte, slicep interface{}) error {
trimmedDoc := bytes.TrimSpace(doc)
switch {
case len(trimmedDoc) == 0:
return errors.New("empty document")
case trimmedDoc[0] == '[':
return json.Unmarshal(doc, slicep)
case trimmedDoc[0] == '{':
s := reflect.ValueOf(slicep).Elem()
s.Set(reflect.MakeSlice(s.Type(), 1, 1))
return json.Unmarshal(doc, s.Index(0).Addr().Interface())
default:
return errors.New("unexpected type")
}
}
Call the function with a pointer to a slice of structs or a pointer to a slice of struct pointers.
var v []Example
err := decodeArrayOrObject(body, &v)
答案2
得分: 0
不确定是否习惯用语,但这段代码可以作为一个示例。
简而言之,你可以尝试以一种格式进行解组,如果失败,则尝试另一种格式。
关键函数是:
func parseStr(data string) ([]Item, error) {
item := Item{}
if err := json.Unmarshal([]byte(data), &item); err == nil {
return []Item{item}, nil
}
items := []Item{}
if err := json.Unmarshal([]byte(data), &items); err != nil {
return nil, errors.New("无效的 JSON 数据")
}
return items, nil
}
英文:
Not sure is idiomatic but this code could be an example.
In short you can try to unmarshal in one format and, if it fails, in the other one
the key function is
func parseStr(data string) ([]Item, error) {
item := Item{}
if err := json.Unmarshal([]byte(data), &item); err == nil {
return []Item{item}, nil
}
items := []Item{}
if err := json.Unmarshal([]byte(data), &items); err != nil {
return nil, errors.New("invalid JSON data")
}
return items, nil
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论