在 go 中解码嵌套的 json 对象
Posted
技术标签:
【中文标题】在 go 中解码嵌套的 json 对象【英文标题】:decoding nested json objects in go 【发布时间】:2015-05-11 15:01:37 【问题描述】:我发现了一些关于如何在 go 中解码 json 嵌套对象的帖子,我尝试将答案应用于我的问题,但我只设法找到了部分解决方案。
我的 json 文件如下所示:
"user":
"gender":"male",
"age":"21-30",
"id":"80b1ea88-19d7-24e8-52cc-65cf6fb9b380"
,
"trials":
"0":"index":0,"word":"WORD 1","Time":3000,"keyboard":true,"train":true,"type":"A",
"1":"index":1,"word":"WORD 2","Time":3000,"keyboard":true,"train":true,"type":"A",
,
"answers":
"training":[
"ans":0,"RT":null,"gtAns":"WORD 1","correct":0,
"ans":0,"RT":null,"gtAns":"WORD 2","correct":0
],
"test":[
"ans":0,"RT":null,"gtAns":true,"correct":0,
"ans":0,"RT":null,"gtAns":true,"correct":0
]
基本上我需要解析其中的信息并将它们保存到 go 结构中。使用下面的代码,我设法提取了用户信息,但对我来说它看起来太复杂了,并且将相同的东西应用于包含 2 个数组且每个数组超过 100 个条目的“答案”字段并不容易。这是我现在使用的代码:
type userDetails struct
Id string `json:"id"`
Age string `json:"age"`
Gender string `json:"gender"`
type jsonRawData map[string]interface
func getJsonContent(r *http.Request) ( userDetails)
defer r.Body.Close()
jsonBody, err := ioutil.ReadAll(r.Body)
var userDataCurr userDetails
if err != nil
log.Printf("Couldn't read request body: %s", err)
else
var f jsonRawData
err := json.Unmarshal(jsonBody, &f)
if err != nil
log.Printf("Error unmashalling: %s", err)
else
user := f["user"].(map[string]interface)
userDataCurr.Id = user["id"].(string)
userDataCurr.Gender = user["gender"].(string)
userDataCurr.Age = user["age"].(string)
return userDataCurr
有什么建议吗?非常感谢!
【问题讨论】:
【参考方案1】:使用interface
并没有利用encoding/json
为您提供的优势,您正在以艰难的方式做到这一点。
我会这样做(注意我假设“gtAns”字段的类型有错误,我将其设为布尔值,您没有提供足够的信息来知道如何处理“ RT”字段):
package main
import (
"encoding/json"
"fmt"
"io"
"log"
"strconv"
"strings"
)
const input = `
"user":
"gender":"male",
"age":"21-30",
"id":"80b1ea88-19d7-24e8-52cc-65cf6fb9b380"
,
"trials":
"0":"index":0,"word":"WORD 1","Time":3000,"keyboard":true,"train":true,"type":"A",
"1":"index":1,"word":"WORD 2","Time":3000,"keyboard":true,"train":true,"type":"A"
,
"answers":
"training":[
"ans":0,"RT":null,"gtAns":true,"correct":0,
"ans":0,"RT":null,"gtAns":true,"correct":0
],
"test":[
"ans":0,"RT":null,"gtAns":true,"correct":0,
"ans":0,"RT":null,"gtAns":true,"correct":0
]
`
type Whatever struct
User struct
Gender Gender `json:"gender"`
Age Range `json:"age"`
ID IDString `json:"id"`
`json:"user"`
Trials map[string]struct
Index int `json:"index"`
Word string `json:"word"`
Time int // should this be a time.Duration?
Train bool `json:"train"`
Type string `json:"type"`
`json:"trials"`
Answers map[string][]struct
Answer int `json:"ans"`
RT json.RawMessage // ??? what type is this
GotAnswer bool `json:"gtAns"`
Correct int `json:"correct"`
`json:"answers"`
// Using some custom types to show custom marshalling:
type IDString string // TODO custom unmarshal and format/error checking
type Gender int
const (
Male Gender = iota
Female
)
func (g *Gender) UnmarshalJSON(b []byte) error
var s string
err := json.Unmarshal(b, &s)
if err != nil
return err
switch strings.ToLower(s)
case "male":
*g = Male
case "female":
*g = Female
default:
return fmt.Errorf("invalid gender %q", s)
return nil
func (g Gender) MarshalJSON() ([]byte, error)
switch g
case Male:
return []byte(`"male"`), nil
case Female:
return []byte(`"female"`), nil
default:
return nil, fmt.Errorf("invalid gender %v", g)
type Range struct Min, Max int
func (r *Range) UnmarshalJSON(b []byte) error
// XXX could be improved
_, err := fmt.Sscanf(string(b), `"%d-%d"`, &r.Min, &r.Max)
return err
func (r Range) MarshalJSON() ([]byte, error)
return []byte(fmt.Sprintf(`"%d-%d"`, r.Min, r.Max)), nil
// Or:
b := make([]byte, 0, 8)
b = append(b, '"')
b = strconv.AppendInt(b, int64(r.Min), 10)
b = append(b, '-')
b = strconv.AppendInt(b, int64(r.Max), 10)
b = append(b, '"')
return b, nil
func fromJSON(r io.Reader) (Whatever, error)
var x Whatever
dec := json.NewDecoder(r)
err := dec.Decode(&x)
return x, err
func main()
// Use http.Get or whatever to get an io.Reader,
// (e.g. response.Body).
// For playground, substitute a fixed string
r := strings.NewReader(input)
// If you actually had a string or []byte:
// var x Whatever
// err := json.Unmarshal([]byte(input), &x)
x, err := fromJSON(r)
if err != nil
log.Fatal(err)
fmt.Println(x)
fmt.Printf("%+v\n", x)
b, err := json.MarshalIndent(x, "", " ")
if err != nil
log.Fatal(err)
fmt.Printf("Re-marshalled: %s\n", b)
Playground
当然,如果你想重用这些子类型,你可以将它们从“Whatever”类型中拉出来,变成它们自己的命名类型。
另外,请注意使用json.Decoder
,而不是提前读入所有数据。通常尽量避免使用ioutil.ReadAll
,除非你真的需要一次所有的数据。
【讨论】:
感谢您的回答,戴夫!实际上使用无论结构帮助我解决问题。关于 ioutil.ReadAll 我尝试直接使用 r.Body 但这不起作用。你建议在 r.Body 上使用 io.Reader 吗? @Dede http.Response 的Body
字段是 io.Reader
。是的,正如我所展示的,通过json.Decoder
使用它。除非您需要请求的其他字段/方法,否则最好将 getJsonContent
函数的参数更改为 io.Reader
(就像我为 fromJSON
所做的那样;或者可能是 io.ReadCloser
; 在以前你会把它留给调用者来关闭请求正文)。这使其更加灵活(例如,您可以将存储的 JSON 的 *os.File
或 strings.Reader
传递给它以进行测试)。
谢谢,我确实做到了你所说的 x, err := fromJSON(r.Body)
。顺便说一下,为了清楚起见,RT 是time.Duration
,gtAnswer(真实答案)是string
。再次感谢以上是关于在 go 中解码嵌套的 json 对象的主要内容,如果未能解决你的问题,请参考以下文章
使用 circe 在 Scala 中 JSON 将嵌套字段解码为 Map[String, String]