在Go语言中解码嵌套的JSON对象。

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

decoding nested json objects in go

问题

我在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结构中。使用下面的代码,我成功提取了用户信息,但对我来说它看起来太复杂了,而且很难将同样的方法应用到包含两个数组且每个数组都有100多个条目的"answers"字段上。以下是我目前正在使用的代码:

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
}

有什么建议吗?非常感谢!

英文:

I found some posts on how to decoding json nested objects in go, I tried to apply the answers to my problem, but I only managed to find a partial solution.

My json file look like this:

  1. {
  2. "user":{
  3. "gender":"male",
  4. "age":"21-30",
  5. "id":"80b1ea88-19d7-24e8-52cc-65cf6fb9b380"
  6. },
  7. "trials":{
  8. "0":{"index":0,"word":"WORD 1","Time":3000,"keyboard":true,"train":true,"type":"A"},
  9. "1":{"index":1,"word":"WORD 2","Time":3000,"keyboard":true,"train":true,"type":"A"},
  10. },
  11. "answers":{
  12. "training":[
  13. {"ans":0,"RT":null,"gtAns":"WORD 1","correct":0},
  14. {"ans":0,"RT":null,"gtAns":"WORD 2","correct":0}
  15. ],
  16. "test":[
  17. {"ans":0,"RT":null,"gtAns":true,"correct":0},
  18. {"ans":0,"RT":null,"gtAns":true,"correct":0}
  19. ]
  20. }
  21. }

Basically I need to parse the information inside it and save them into go structure. With the code below I managed to extract the user information, but it looks too complicated to me and it won't be easy to apply the same thing to the "answers" fields which contains 2 arrays with more than 100 entries each. Here the code I'm using now:

  1. type userDetails struct {
  2. Id string `json:"id"`
  3. Age string `json:"age"`
  4. Gender string `json:"gender"`
  5. }
  6. type jsonRawData map[string]interface {
  7. }
  8. func getJsonContent(r *http.Request) ( userDetails) {
  9. defer r.Body.Close()
  10. jsonBody, err := ioutil.ReadAll(r.Body)
  11. var userDataCurr userDetails
  12. if err != nil {
  13. log.Printf("Couldn't read request body: %s", err)
  14. } else {
  15. var f jsonRawData
  16. err := json.Unmarshal(jsonBody, &f)
  17. if err != nil {
  18. log.Printf("Error unmashalling: %s", err)
  19. } else {
  20. user := f["user"].(map[string]interface{})
  21. userDataCurr.Id = user["id"].(string)
  22. userDataCurr.Gender = user["gender"].(string)
  23. userDataCurr.Age = user["age"].(string)
  24. }
  25. }
  26. return userDataCurr
  27. }

Any suggestions? Thanks a lot!

答案1

得分: 3

你正在以一种困难的方式使用interface{},而没有充分利用encoding/json提供的功能。

我会这样做(请注意,我假设“gtAns”字段的类型有错误,并将其更改为布尔值,你没有提供足够的信息来确定如何处理“RT”字段):

  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io"
  6. "log"
  7. "strconv"
  8. "strings"
  9. )
  10. const input = `{
  11. "user":{
  12. "gender":"male",
  13. "age":"21-30",
  14. "id":"80b1ea88-19d7-24e8-52cc-65cf6fb9b380"
  15. },
  16. "trials":{
  17. "0":{"index":0,"word":"WORD 1","Time":3000,"keyboard":true,"train":true,"type":"A"},
  18. "1":{"index":1,"word":"WORD 2","Time":3000,"keyboard":true,"train":true,"type":"A"}
  19. },
  20. "answers":{
  21. "training":[
  22. {"ans":0,"RT":null,"gtAns":true,"correct":0},
  23. {"ans":0,"RT":null,"gtAns":true,"correct":0}
  24. ],
  25. "test":[
  26. {"ans":0,"RT":null,"gtAns":true,"correct":0},
  27. {"ans":0,"RT":null,"gtAns":true,"correct":0}
  28. ]
  29. }
  30. }`
  31. type Whatever struct {
  32. User struct {
  33. Gender Gender `json:"gender"`
  34. Age Range `json:"age"`
  35. ID IDString `json:"id"`
  36. } `json:"user"`
  37. Trials map[string]struct {
  38. Index int `json:"index"`
  39. Word string `json:"word"`
  40. Time int // should this be a time.Duration?
  41. Keyboard bool `json:"keyboard"`
  42. Train bool `json:"train"`
  43. Type string `json:"type"`
  44. } `json:"trials"`
  45. Answers map[string][]struct {
  46. Answer int `json:"ans"`
  47. RT json.RawMessage // ??? what type is this
  48. GotAnswer bool `json:"gtAns"`
  49. Correct int `json:"correct"`
  50. } `json:"answers"`
  51. }
  52. // Using some custom types to show custom marshalling:
  53. type IDString string // TODO custom unmarshal and format/error checking
  54. type Gender int
  55. const (
  56. Male Gender = iota
  57. Female Gender = iota
  58. )
  59. func (g *Gender) UnmarshalJSON(b []byte) error {
  60. var s string
  61. err := json.Unmarshal(b, &s)
  62. if err != nil {
  63. return err
  64. }
  65. switch strings.ToLower(s) {
  66. case "male":
  67. *g = Male
  68. case "female":
  69. *g = Female
  70. default:
  71. return fmt.Errorf("invalid gender %q", s)
  72. }
  73. return nil
  74. }
  75. func (g Gender) MarshalJSON() ([]byte, error) {
  76. switch g {
  77. case Male:
  78. return []byte(`"male"`), nil
  79. case Female:
  80. return []byte(`"female"`), nil
  81. default:
  82. return nil, fmt.Errorf("invalid gender %v", g)
  83. }
  84. }
  85. type Range struct{ Min, Max int }
  86. func (r *Range) UnmarshalJSON(b []byte) error {
  87. // XXX could be improved
  88. _, err := fmt.Sscanf(string(b), `"%d-%d"`, &r.Min, &r.Max)
  89. return err
  90. }
  91. func (r Range) MarshalJSON() ([]byte, error) {
  92. return []byte(fmt.Sprintf(`"%d-%d"`, r.Min, r.Max)), nil
  93. // Or:
  94. b := make([]byte, 0, 8)
  95. b = append(b, '"')
  96. b = strconv.AppendInt(b, int64(r.Min), 10)
  97. b = append(b, '-')
  98. b = strconv.AppendInt(b, int64(r.Max), 10)
  99. b = append(b, '"')
  100. return b, nil
  101. }
  102. func fromJSON(r io.Reader) (Whatever, error) {
  103. var x Whatever
  104. dec := json.NewDecoder(r)
  105. err := dec.Decode(&x)
  106. return x, err
  107. }
  108. func main() {
  109. // Use http.Get or whatever to get an io.Reader,
  110. // (e.g. response.Body).
  111. // For playground, substitute a fixed string
  112. r := strings.NewReader(input)
  113. // If you actually had a string or []byte:
  114. // var x Whatever
  115. // err := json.Unmarshal([]byte(input), &x)
  116. x, err := fromJSON(r)
  117. if err != nil {
  118. log.Fatal(err)
  119. }
  120. fmt.Println(x)
  121. fmt.Printf("%+v\n", x)
  122. b, err := json.MarshalIndent(x, "", " ")
  123. if err != nil {
  124. log.Fatal(err)
  125. }
  126. fmt.Printf("Re-marshalled: %s\n", b)
  127. }

当然,如果你想重用这些子类型,你可以将它们从“Whatever”类型中提取出来,成为自己的命名类型。

另外,请注意使用json.Decoder而不是提前读取所有数据。通常尽量避免使用ioutil.ReadAll,除非你真的需要一次性获取所有数据。

英文:

You're doing it the hard way by using interface{} and not taking advantage of what encoding/json gives you.

I'd do it something like this (note I assumed there was an error with the type of the "gtAns" field and I made it a boolean, you don't give enough information to know what to do with the "RT" field):

  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io"
  6. "log"
  7. "strconv"
  8. "strings"
  9. )
  10. const input = `{
  11. "user":{
  12. "gender":"male",
  13. "age":"21-30",
  14. "id":"80b1ea88-19d7-24e8-52cc-65cf6fb9b380"
  15. },
  16. "trials":{
  17. "0":{"index":0,"word":"WORD 1","Time":3000,"keyboard":true,"train":true,"type":"A"},
  18. "1":{"index":1,"word":"WORD 2","Time":3000,"keyboard":true,"train":true,"type":"A"}
  19. },
  20. "answers":{
  21. "training":[
  22. {"ans":0,"RT":null,"gtAns":true,"correct":0},
  23. {"ans":0,"RT":null,"gtAns":true,"correct":0}
  24. ],
  25. "test":[
  26. {"ans":0,"RT":null,"gtAns":true,"correct":0},
  27. {"ans":0,"RT":null,"gtAns":true,"correct":0}
  28. ]
  29. }
  30. }`
  31. type Whatever struct {
  32. User struct {
  33. Gender Gender `json:"gender"`
  34. Age Range `json:"age"`
  35. ID IDString `json:"id"`
  36. } `json:"user"`
  37. Trials map[string]struct {
  38. Index int `json:"index"`
  39. Word string `json:"word"`
  40. Time int // should this be a time.Duration?
  41. Train bool `json:"train"`
  42. Type string `json:"type"`
  43. } `json:"trials"`
  44. Answers map[string][]struct {
  45. Answer int `json:"ans"`
  46. RT json.RawMessage // ??? what type is this
  47. GotAnswer bool `json:"gtAns"`
  48. Correct int `json:"correct"`
  49. } `json:"answers"`
  50. }
  51. // Using some custom types to show custom marshalling:
  52. type IDString string // TODO custom unmarshal and format/error checking
  53. type Gender int
  54. const (
  55. Male Gender = iota
  56. Female
  57. )
  58. func (g *Gender) UnmarshalJSON(b []byte) error {
  59. var s string
  60. err := json.Unmarshal(b, &s)
  61. if err != nil {
  62. return err
  63. }
  64. switch strings.ToLower(s) {
  65. case "male":
  66. *g = Male
  67. case "female":
  68. *g = Female
  69. default:
  70. return fmt.Errorf("invalid gender %q", s)
  71. }
  72. return nil
  73. }
  74. func (g Gender) MarshalJSON() ([]byte, error) {
  75. switch g {
  76. case Male:
  77. return []byte(`"male"`), nil
  78. case Female:
  79. return []byte(`"female"`), nil
  80. default:
  81. return nil, fmt.Errorf("invalid gender %v", g)
  82. }
  83. }
  84. type Range struct{ Min, Max int }
  85. func (r *Range) UnmarshalJSON(b []byte) error {
  86. // XXX could be improved
  87. _, err := fmt.Sscanf(string(b), `"%d-%d"`, &r.Min, &r.Max)
  88. return err
  89. }
  90. func (r Range) MarshalJSON() ([]byte, error) {
  91. return []byte(fmt.Sprintf(`"%d-%d"`, r.Min, r.Max)), nil
  92. // Or:
  93. b := make([]byte, 0, 8)
  94. b = append(b, '"')
  95. b = strconv.AppendInt(b, int64(r.Min), 10)
  96. b = append(b, '-')
  97. b = strconv.AppendInt(b, int64(r.Max), 10)
  98. b = append(b, '"')
  99. return b, nil
  100. }
  101. func fromJSON(r io.Reader) (Whatever, error) {
  102. var x Whatever
  103. dec := json.NewDecoder(r)
  104. err := dec.Decode(&x)
  105. return x, err
  106. }
  107. func main() {
  108. // Use http.Get or whatever to get an io.Reader,
  109. // (e.g. response.Body).
  110. // For playground, substitute a fixed string
  111. r := strings.NewReader(input)
  112. // If you actually had a string or []byte:
  113. // var x Whatever
  114. // err := json.Unmarshal([]byte(input), &x)
  115. x, err := fromJSON(r)
  116. if err != nil {
  117. log.Fatal(err)
  118. }
  119. fmt.Println(x)
  120. fmt.Printf("%+v\n", x)
  121. b, err := json.MarshalIndent(x, "", " ")
  122. if err != nil {
  123. log.Fatal(err)
  124. }
  125. fmt.Printf("Re-marshalled: %s\n", b)
  126. }

<kbd>Playground</kbd>

Of course if you want to reuse those sub-types you could pull them out of the "Whatever" type into their own named types.

Also, note the use of a json.Decoder rather than reading in all the data ahead of time. Usually try and avoid any use of ioutil.ReadAll unless you really need all the data at once.

huangapple
  • 本文由 发表于 2015年5月11日 23:01:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/30171134.html
匿名

发表评论

匿名网友

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

确定