dapaul
dapaul

Reputation: 25

Original order of JSON values

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": [
                "[email protected]",
                "[email protected]"
            ],
            "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
[email protected] is string

You have some ideas for it? Thanks you.

Upvotes: 0

Views: 1299

Answers (2)

Gopher
Gopher

Reputation: 751

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

Upvotes: 0

icza
icza

Reputation: 418745

By default the encoding/json package uses Go maps to unmarshal JSON objects. Go maps are not ordered, see Why can't Go iterate maps in insertion order? and In Golang, why are iterations over maps random?

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

Upvotes: 5

Related Questions