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

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

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:

{
"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}
		]
	}
}

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:

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
}

Any suggestions? Thanks a lot!

答案1

得分: 3

你正在以一种困难的方式使用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?
		Keyboard bool   `json:"keyboard"`
		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 Gender = iota
)

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)

}

当然,如果你想重用这些子类型,你可以将它们从“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):

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)
}

<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:

确定