JSON值的原始顺序

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

Original order of JSON values

问题

在我的应用程序中,我得到了一些未指定的 JSON 字符串,并希望循环遍历 JSON 字符串的值。我使用函数 json.Unmarshal() 从 JSON 值中获取一个对象列表。这个方法很好用。但不幸的是,我得到的是 JSON 值的随机列表,而不是它们的原始顺序。我使用了以下示例代码:

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	jsonstr := `{
    "@odata.context": "http://services.odata.org/V4/TripPinService/$metadata#People/$entity",
    "@odata.id": "http://services.odata.org/V4/TripPinService/People('russellwhyte')",
    "@odata.etag": "W/\"08D956FAB7E22152\"",
    "@odata.editLink": "http://services.odata.org/V4/TripPinService/People('russellwhyte')",
    "UserName": "russellwhyte",
    "FirstName": "Russell",
    "LastName": "Whyte",
    "Gender": "Male",
    "Concurrency": 637636457076498770
}`

	var result interface{}
	if err := json.Unmarshal([]byte(jsonstr), &result); err != nil {
		fmt.Println("Can't convert json to object.")
		fmt.Println(err.Error())
	}
	odataobjs := result.(map[string]interface{})
	for k, v := range odataobjs {
		fmt.Print(fmt.Sprintln(k, v))
	}
}

你可以在 go-playground 上查看这个示例。

这是一些结果列表中的内容:

@odata.editLink http://services.odata.org/V4/TripPinService/People('russellwhyte')
UserName russellwhyte
Gender Male
Concurrency 6.376364570764988e+17
@odata.id http://services.odata.org/V4/TripPinService/People('russellwhyte')
@odata.etag W/"08D956FAB7E22152"
LastName Whyte
@odata.context http://services.odata.org/V4/TripPinService/$metadata#People/$entity
FirstName Russell

在第一次在网上搜索后,我发现 JSON 对象通常是一个无序列表。这对我来说没问题,只要列表按照原始顺序排列!我不能构建一些结构体,因为 JSON 值在运行时是未知的!

你有什么办法可以获取原始值列表吗?

谢谢你的输入。我现在有了原始顺序。
但是:我如何找出值的类型?字符串、nil、整数和浮点数都可以,我可以得到这些类型。但是如何通过这些解决方案找出 JSON 文件中的数组?就像下面的 JSON,他说 Emails 是一个未知类型。而 AddressInfo 没有列出来?所以我无法获取它的子元素。

JSON 输入:

{
    "@odata.context": "http://services.odata.org/V4/TripPinService/$metadata#People",
    "value": [
        {
            "@odata.id": "http://services.odata.org/V4/TripPinService/People('vincentcalabrese')",
            "@odata.etag": "W/\"08D957CD4BA2C90E\"",
            "@odata.editLink": "http://services.odata.org/V4/TripPinService/People('vincentcalabrese')",
            "UserName": "vincentcalabrese",
            "FirstName": "Vincent",
            "LastName": "Calabrese",
            "Emails": [
                "Vincent@example.com",
                "Vincent@contoso.com"
            ],
            "AddressInfo": [
                {
                    "Address": "55 Grizzly Peak Rd.",
                    "City": {
                        "CountryRegion": "United States",
                        "Name": "Butte",
                        "Region": "MT"
                    }
                }
            ],
            "Gender": "Male",
            "Concurrency": 637637361498507534
        }
    ]
}

示例代码:

dec := json.NewDecoder(strings.NewReader(jsonstr))

for dec.More() {
	// Read prop name
	t, err := dec.Token()
	if err != nil {
		log.Printf("Err: %v", err)
		break
	}
	var name string
	var ok bool
	if name, ok = t.(string); !ok {
		continue // May be a delimeter
	}

	// Read value:
	t, err = dec.Token()
	if err != nil {
		log.Printf("Err: %v", err)
		break
	}
	//fmt.Printf("Name: %s, Value: %v\n", name, t)

	switch t.(type) {
	case nil:
		logger.Debug(fmt.Sprintf("%s is nil", name))
	case string:
		logger.Debug(fmt.Sprintf("%s is string", name))
	case []interface{}:
		logger.Debug(fmt.Sprintf("%s is array", name))
	case map[string]interface{}:
		logger.Debug(fmt.Sprintf("%s is map", name))
	case int16:
		logger.Debug(fmt.Sprintf("%s is int16", name))
	case int32:
		logger.Debug(fmt.Sprintf("%s is int32", name))
	case int64:
		logger.Debug(fmt.Sprintf("%s is int64", name))
	case float32:
		logger.Debug(fmt.Sprintf("%s is float32", name))
	case float64:
		logger.Debug(fmt.Sprintf("%s is float64", name))
	default:
		logger.Debug(fmt.Sprintf("%s is unknown type", name))
	}
}

输出:

@odata.context is string
value is unknown type
@odata.id is string
@odata.etag is string
@odata.editLink is string
UserName is string
FirstName is string
LastName is string
Emails is unknown type
Vincent@example.com is string

你对此有什么想法吗?
谢谢你。

英文:

In my application, I get some unspecified json string and want to loop through the values of the json string. I use the function json.Unmarshal() to get an objectlist from the json values. That works fine. But unfortnely I get an random list of the json values and not the orginal order of them. I use this example code:

package main
import (
"encoding/json"
"fmt"
)
func main() {
jsonstr := `{
"@odata.context": "http://services.odata.org/V4/TripPinService/$metadata#People/$entity",
"@odata.id": "http://services.odata.org/V4/TripPinService/People('russellwhyte')",
"@odata.etag": "W/\"08D956FAB7E22152\"",
"@odata.editLink": "http://services.odata.org/V4/TripPinService/People('russellwhyte')",
"UserName": "russellwhyte",
"FirstName": "Russell",
"LastName": "Whyte",
"Gender": "Male",
"Concurrency": 637636457076498770
}`
var result interface{}
if err := json.Unmarshal([]byte(jsonstr), &result); err != nil {
fmt.Println("Can't convert json to object.")
fmt.Println(err.Error())
}
odataobjs := result.(map[string]interface{})
for k, v := range odataobjs {
fmt.Print(fmt.Sprintln(k, v))
}
}

See on go-playground.

This can be some of the result list:

@odata.editLink http://services.odata.org/V4/TripPinService/People('russellwhyte')
UserName russellwhyte
Gender Male
Concurrency 6.376364570764988e+17
@odata.id http://services.odata.org/V4/TripPinService/People('russellwhyte')
@odata.etag W/"08D956FAB7E22152"
LastName Whyte
@odata.context http://services.odata.org/V4/TripPinService/$metadata#People/$entity
FirstName Russell

After first search in www I find out, that a json object is in general an unordered list. Thats okay for me, as long as the list is in original order! I can't build some struct, because the json values are unknown at the runtime!

Have you some ideas for me to get the original value list?

Thanks for this input. I have now the original order.
But: How can i find out the type of the values? string, nil int and float are okay and i get this. But how can i find out the arrays in the json file with this solutions? Like the following JSON, he says that Emails is an unknown type. And AddressInfo is not listet? So i can't get the sub elements from it.

Json Input:

{
"@odata.context": "http://services.odata.org/V4/TripPinService/$metadata#People",
"value": [
{
"@odata.id": "http://services.odata.org/V4/TripPinService/People('vincentcalabrese')",
"@odata.etag": "W/\"08D957CD4BA2C90E\"",
"@odata.editLink": "http://services.odata.org/V4/TripPinService/People('vincentcalabrese')",
"UserName": "vincentcalabrese",
"FirstName": "Vincent",
"LastName": "Calabrese",
"Emails": [
"Vincent@example.com",
"Vincent@contoso.com"
],
"AddressInfo": [
{
"Address": "55 Grizzly Peak Rd.",
"City": {
"CountryRegion": "United States",
"Name": "Butte",
"Region": "MT"
}
}
],
"Gender": "Male",
"Concurrency": 637637361498507534
}
]
}

Example Code:

dec := json.NewDecoder(strings.NewReader(jsonstr))
for dec.More() {
// Read prop name
t, err := dec.Token()
if err != nil {
log.Printf("Err: %v", err)
break
}
var name string
var ok bool
if name, ok = t.(string); !ok {
continue // May be a delimeter
}
// Read value:
t, err = dec.Token()
if err != nil {
log.Printf("Err: %v", err)
break
}
//fmt.Printf("Name: %s, Value: %v\n", name, t)
switch t.(type) {
case nil:
logger.Debug(fmt.Sprintf("%s is nil", name))
case string:
logger.Debug(fmt.Sprintf("%s is string", name))
case []interface{}:
logger.Debug(fmt.Sprintf("%s is array", name))
case map[string]interface{}:
logger.Debug(fmt.Sprintf("%s is map", name))
case int16:
logger.Debug(fmt.Sprintf("%s is int16", name))
case int32:
logger.Debug(fmt.Sprintf("%s is int32", name))
case int64:
logger.Debug(fmt.Sprintf("%s is int64", name))
case float32:
logger.Debug(fmt.Sprintf("%s is float32", name))
case float64:
logger.Debug(fmt.Sprintf("%s is float64", name))
default:
logger.Debug(fmt.Sprintf("%s is unknown type", name))
}
}

Output

@odata.context is string
value is unknown type
@odata.id is string
@odata.etag is string
@odata.editLink is string
UserName is string
FirstName is string
LastName is string
Emails is unknown type
Vincent@example.com is string

You have some ideas for it?
Thanks you.

答案1

得分: 5

默认情况下,encoding/json 包使用 Go 的 map 来解析 JSON 对象。Go 的 map 是无序的,参见 https://stackoverflow.com/questions/28930416/why-cant-go-iterate-maps-in-insertion-order/28931555#28931555https://stackoverflow.com/questions/55925822/in-golang-why-are-iterations-over-maps-random/55925880#55925880

因此,如果你需要保持原始顺序,就不能使用 map(隐式或显式)。你可以使用 json.Decoder 并按照标记解码输入,这样可以按照原始顺序获取标记。

以下是你的示例代码:

dec := json.NewDecoder(strings.NewReader(jsonstr))
for dec.More() {
// 读取属性名
t, err := dec.Token()
if err != nil {
log.Printf("错误:%v", err)
break
}
var name string
var ok bool
if name, ok = t.(string); !ok {
continue // 可能是分隔符
}
// 读取值:
t, err = dec.Token()
if err != nil {
log.Printf("错误:%v", err)
break
}
fmt.Printf("属性名:%s,值:%v\n", name, t)
}

这将输出以下结果(在 Go Playground 上尝试):

属性名:@odata.context,值:http://services.odata.org/V4/TripPinService/$metadata#People/$entity
属性名:@odata.id,值:http://services.odata.org/V4/TripPinService/People('russellwhyte')
属性名:@odata.etag,值:W/"08D956FAB7E22152"
属性名:@odata.editLink,值:http://services.odata.org/V4/TripPinService/People('russellwhyte')
属性名:UserName,值:russellwhyte
属性名:FirstName,值:Russell
属性名:LastName,值:Whyte
属性名:Gender,值:Male
属性名:Concurrency,值:6.376364570764988e+17
英文:

By default the encoding/json package uses Go maps to unmarshal JSON objects. Go maps are not ordered, see https://stackoverflow.com/questions/28930416/why-cant-go-iterate-maps-in-insertion-order/28931555#28931555 and https://stackoverflow.com/questions/55925822/in-golang-why-are-iterations-over-maps-random/55925880#55925880

So if you need the original order, you cannot use maps (implicit or explicit). You may use json.Decoder and decode the input by tokens, which of course gives you the tokens in the original order.

This is how it could look like to your example:

dec := json.NewDecoder(strings.NewReader(jsonstr))
for dec.More() {
// Read prop name
t, err := dec.Token()
if err != nil {
log.Printf("Err: %v", err)
break
}
var name string
var ok bool
if name, ok = t.(string); !ok {
continue // May be a delimeter
}
// Read value:
t, err = dec.Token()
if err != nil {
log.Printf("Err: %v", err)
break
}
fmt.Printf("Name: %s, Value: %v\n", name, t)
}

This will output (try it on the Go Playground):

Name: @odata.context, Value: http://services.odata.org/V4/TripPinService/$metadata#People/$entity
Name: @odata.id, Value: http://services.odata.org/V4/TripPinService/People('russellwhyte')
Name: @odata.etag, Value: W/"08D956FAB7E22152"
Name: @odata.editLink, Value: http://services.odata.org/V4/TripPinService/People('russellwhyte')
Name: UserName, Value: russellwhyte
Name: FirstName, Value: Russell
Name: LastName, Value: Whyte
Name: Gender, Value: Male
Name: Concurrency, Value: 6.376364570764988e+17

答案2

得分: 0

使用sort包,你可以按升序获取JSON数据,而不是随机列表。你需要创建一个映射键的切片,并在其上使用sort.Strings()方法,然后你可以迭代切片并按升序获取数据。以下是相应的代码:

package main

import (
	"encoding/json"
	"fmt"
	"sort"
)

func main() {

	jsonstr := `{
            "@odata.context": "http://services.odata.org/V4/TripPinService/$metadata#People/$entity",
            "@odata.id": "http://services.odata.org/V4/TripPinService/People('russellwhyte')",
            "@odata.etag": "W/\"08D956FAB7E22152\"",
            "@odata.editLink": "http://services.odata.org/V4/TripPinService/People('russellwhyte')",
            "UserName": "russellwhyte",
            "FirstName": "Russell",
            "LastName": "Whyte",
            "Gender": "Male",
            "Concurrency": 637636457076498770
        }`

	var result interface{}
	if err := json.Unmarshal([]byte(jsonstr), &result); err != nil {
		fmt.Println("无法将JSON转换为对象。")
		fmt.Println(err.Error())
	}
	odataobjs := result.(map[string]interface{})

	keysJson := make([]string, 0, len(odataobjs))
	for k := range odataobjs {
		keysJson = append(keysJson, k)
	}

	sort.Strings(keysJson)

	for _, k2 := range keysJson {
		fmt.Println(k2, odataobjs[k2])
	}
}

输出结果:

@odata.context http://services.odata.org/V4/TripPinService/$metadata#People/$entity
@odata.editLink http://services.odata.org/V4/TripPinService/People('russellwhyte')
@odata.etag W/"08D956FAB7E22152"
@odata.id http://services.odata.org/V4/TripPinService/People('russellwhyte')
Concurrency 6.376364570764988e+17
FirstName Russell
Gender Male
LastName Whyte
UserName russellwhyte
英文:

Instead of getting the json as random list ,you can get it in ascending order using sort package.You need to create a slice of your map keys and use the sort.Strings() method on it ,then you can iterate your slice and get the data in ascending order.Here is the code for the same:

package main

import (
	"encoding/json"
	"fmt"
	"sort"
)

func main() {

	jsonstr := `{
            "@odata.context": "http://services.odata.org/V4/TripPinService/$metadata#People/$entity",
            "@odata.id": "http://services.odata.org/V4/TripPinService/People('russellwhyte')",
            "@odata.etag": "W/\"08D956FAB7E22152\"",
            "@odata.editLink": "http://services.odata.org/V4/TripPinService/People('russellwhyte')",
            "UserName": "russellwhyte",
            "FirstName": "Russell",
            "LastName": "Whyte",
            "Gender": "Male",
            "Concurrency": 637636457076498770
        }`

	var result interface{}
	if err := json.Unmarshal([]byte(jsonstr), &result); err != nil {
		fmt.Println("Can't convert json to object.")
		fmt.Println(err.Error())
	}
	odataobjs := result.(map[string]interface{})

	keysJson := make([]string, 0, len(odataobjs))
	for k := range odataobjs {
		keysJson = append(keysJson, k)
	}

	sort.Strings(keysJson)

	for _, k2 := range keysJson {
		fmt.Println(k2, odataobjs[k2])
	}
}

Output:

@odata.context http://services.odata.org/V4/TripPinService/$metadata#People/$entity
@odata.editLink http://services.odata.org/V4/TripPinService/People('russellwhyte')
@odata.etag W/"08D956FAB7E22152"
@odata.id http://services.odata.org/V4/TripPinService/People('russellwhyte')
Concurrency 6.376364570764988e+17
FirstName Russell
Gender Male
LastName Whyte
UserName russellwhyte

huangapple
  • 本文由 发表于 2021年8月5日 13:34:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/68661084.html
匿名

发表评论

匿名网友

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

确定