如何格式化具有多个对象返回的json结构? (动态)

如何格式化具有多个对象返回的json结构?  (动态)

问题描述:

I have an API call and it returns like this:

{
    "result": {
        "720268538": {
            "icon_url": "-9a81dlWLwJ2UUGcVs_nsVtzdOEdtWwKGZZLQHTxDZ7I56KU0Zwwo4NUX4oFJZEHLbXU5A1PIYQNqhpOSV-fRPasw8rsUFJ5KBFZv668FFEuh_KQJTtEuI63xIXbxqOtauyClTMEsJV1jruS89T3iQKx_BBqa2j3JpjVLFH1xpp0EQ",
            "icon_url_large": "",
            "icon_drag_url": "",
            "name": "Chroma Case",
            "market_hash_name": "Chroma Case",
            "market_name": "Chroma Case",
            "name_color": "D2D2D2",
            "background_color": "",
            "type": "Base Grade Container",
            "tradable": "1",
            "marketable": "1",
            "commodity": "1",
            "market_tradable_restriction": "7",
            "fraudwarnings": "",
            "descriptions": {
                "0": {
                    "type": "html",
                    "value": " ",
                    "app_data": ""
                },
                "1": {
                    "type": "html",
                    "value": "Container Series #38",
                    "color": "99ccff",
                    "app_data": ""
                },
                "2": {
                    "type": "html",
                    "value": " ",
                    "app_data": ""
                },
                "3": {
                    "type": "html",
                    "value": "Contains one of the following:",
                    "app_data": ""
                },
                "4": {
                    "type": "html",
                    "value": "Glock-18 | Catacombs",
                    "color": "4b69ff",
                    "app_data": ""
                },
                "5": {
                    "type": "html",
                    "value": "M249 | System Lock",
                    "color": "4b69ff",
                    "app_data": ""
                },
                "6": {
                    "type": "html",
                    "value": "MP9 | Deadly Poison",
                    "color": "4b69ff",
                    "app_data": ""
                },
                "7": {
                    "type": "html",
                    "value": "SCAR-20 | Grotto",
                    "color": "4b69ff",
                    "app_data": ""
                },
                "8": {
                    "type": "html",
                    "value": "XM1014 | Quicksilver",
                    "color": "4b69ff",
                    "app_data": ""
                },
                "9": {
                    "type": "html",
                    "value": "Dual Berettas | Urban Shock",
                    "color": "8847ff",
                    "app_data": ""
                },
                "10": {
                    "type": "html",
                    "value": "Desert Eagle | Naga",
                    "color": "8847ff",
                    "app_data": ""
                },
                "11": {
                    "type": "html",
                    "value": "MAC-10 | Malachite",
                    "color": "8847ff",
                    "app_data": ""
                },
                "12": {
                    "type": "html",
                    "value": "Sawed-Off | Serenity",
                    "color": "8847ff",
                    "app_data": ""
                },
                "13": {
                    "type": "html",
                    "value": "AK-47 | Cartel",
                    "color": "d32ce6",
                    "app_data": ""
                },
                "14": {
                    "type": "html",
                    "value": "M4A4 | 龍王 (Dragon King)",
                    "color": "d32ce6",
                    "app_data": ""
                },
                "15": {
                    "type": "html",
                    "value": "P250 | Muertos",
                    "color": "d32ce6",
                    "app_data": ""
                },
                "16": {
                    "type": "html",
                    "value": "AWP | Man-o'-war",
                    "color": "eb4b4b",
                    "app_data": ""
                },
                "17": {
                    "type": "html",
                    "value": "Galil AR | Chatterbox",
                    "color": "eb4b4b",
                    "app_data": ""
                },
                "18": {
                    "type": "html",
                    "value": "or an Exceedingly Rare Special Item!",
                    "color": "ffd700",
                    "app_data": ""
                },
                "19": {
                    "type": "html",
                    "value": " ",
                    "app_data": ""
                },
                "20": {
                    "type": "html",
                    "value": "",
                    "color": "00a000",
                    "app_data": {
                        "limited": "1"
                    }
                }
            },
            "owner_descriptions": "",
            "tags": {
                "0": {
                    "internal_name": "CSGO_Type_WeaponCase",
                    "name": "Container",
                    "category": "Type",
                    "category_name": "Type"
                },
                "1": {
                    "internal_name": "set_community_6",
                    "name": "The Chroma Collection",
                    "category": "ItemSet",
                    "category_name": "Collection"
                },
                "2": {
                    "internal_name": "normal",
                    "name": "Normal",
                    "category": "Quality",
                    "category_name": "Category"
                },
                "3": {
                    "internal_name": "Rarity_Common",
                    "name": "Base Grade",
                    "category": "Rarity",
                    "color": "b0c3d9",
                    "category_name": "Quality"
                }
            },
            "classid": "720268538"
        },
        "success": true
    }
}

Result can have multiple returns I was wondering how the struct should look, this is what I have so far but it returns like nothing.

type AssetInfo struct {
    Result `json:"result"`
}

type Result struct {
    Asset   map[string]Asset `json:"asset"`
    Success bool             `json:"success,omitempty"`
}

type Asset struct {
    IconUrl           string                   `json:"icon_url,omitempty"`
    IconUrlLarge      string                   `json:"icon_url_large,omitempty"`
    IconDragUrl       string                   `json:"icon_drag_url,omitempty"`
    Name              string                   `json:"name,omitempty"`
    MarketHashName    string                   `json:"market_hash_name,omitempty"`
    MarketName        string                   `json:"market_name,omitempty"`
    NameColor         string                   `json:"name_color,omitempty"`
    BGColor           string                   `json:"background_color,omitempty"`
    Type              string                   `json:"type,omitempty"`
    Tradable          string                   `json:"tradable,omitempty"`
    Marketable        string                   `json:"marketable,omitempty"`
    Commodity         string                   `json:"commodity,omitempty"`
    TradeRestrict     string                   `json:"market_tradeable_restriction,omitempty"`
    FraudWarnings     string                   `json:"fraudwarnings,omitempty"`
    Descriptions      map[string]*Descriptions `json:"descriptions,omitempty"`
    OwnerDescriptions string                   `json:"owner_descriptions,omitempty"`
    Tags              map[string]*Tags         `json:"tags,omitempty"`
    ClassId           string                   `json:"classid,omitempty"`
}

type Descriptions struct {
    Type    string `json:"type"`
    Value   string `json:"value"`
    Color   string `json:"color,omitempty"`
    AppData string `json:"appdata"`
}

type Tags struct {
    InternalName string `json:"internal_name"`
    Name         string `json:"name"`
    Category     string `json:"category"`
    Color        string `json:"color,omitempty"`
    CategoryName string `json:"category_name"`
}

If anybody could tell me what's wrong with my struct that would be great thanks.

The thing that confuses me is like how Descriptions returns not an array but multiple objects that could range from 0-20 how do I prepare a struct for this when I don't know how many objects are going to return, as well result can return multiple "720616831" so how should this look?

You're going to kick yourself for the first mistake -- your JSON has result but your struct tag has response.

The second problem is a trickier one. The thing is that you declared that your Asset map is nested inside the result as a key called "asset", but that's not true. Actually it's just all of the keys of the result other than success/error. Unfortunately, encoding/json doesn't have any way to express this. You can say that result is a map[string]interface{}, and then success/error if they exist will be bool/string, and the assets will be more map[string]interface{}s containing keys for all of the other fields. That works, but it's a bit ugly/inefficient, and a lot of work if you want to convert into a proper struct type so that you can have methods on it.

Maybe a better way to go is to decode into this type:

type AssetIntermediate struct {
    Result map[string]json.RawMessage `json:"result"`
}

as well as having

type Asset struct { /* all those fields */ }
type AssetInfo struct {
    Success bool
    Error string
    Assets map[string]Asset
}

Then you can do

var intermediate AssetIntermediate
err := json.Unmarshal(data, &intermediate)
/* handle err */

var ai AssetInfo

/* At this point, intermediate.Result is a map
 * of strings to json.RawMessage, which is just a []byte
 * containing not-yet-decoded JSON. We want to take
 * each field and put it into ai where it belongs.
 */
for k, v := range intermediate.Result {
    var err error
    // error and success keys are decoded into the respective fields
    if k == "error" {
        err = json.Unmarshal(v, &ai.Error)
    } else if k == "success" {
        err = json.Unmarshal(v, &ai.Success)
    } else {
        // Otherwise, we have an asset. First decode it...
        var asset Asset
        err = json.Unmarshal(v, &asset)
        if err == nil {
            // Create the Assets map if it doesn't exist yet
            if ai.Assets == nil {
                ai.Assets = map[string]Asset{}
            }
            // And store the asset in the map under the key k.
            ai.Assets[k] = asset
        }
    }
    /* handle err if non-nil */
}

which is a lot more work than a single Decode call, but it should basically do the right thing.

If all of this is inside a function that returns (*AssetInfo, error) then the right thing to put in place of /* Handle err */ might be

if err != nil {
    return nil, err
}

I suggest playing with the base map[string]interface{} type and iterating from the json string until you can build the whole struct. For the parts where you don't really know the keys, such as the 0-20, the map may be all you'll have.

For example, suppose the string your example is in variable j. You could start with:

type AssetInfo struct {
    Result map[string]interface{}
}

and start unmarshaling and printing in order to find the full struct:

var ai AssetInfo
json.Unmarshal([]byte(j), &ai)
fmt.Printf("%#v
", ai)

This prints the entire map built after unmarshaling. Trying to build the whole struct beforehand with such a complex structure is unlikely to get you results.

EDIT: If you treat Result as a map[string]struct like you did with Descriptions the Success bool is "lost" and goes into the map but the rest of the json unmarshals correctly. I don't see a way to support the Success bool as well as a map for the rest of the structure. As for the numbered keys, if you need a slice, you'll have to do some work after the unmarshal.

type AssetInfo struct {
    Result map[string]Numbered
}

type Numbered struct {
    IconUrl          string                 `json:"icon_url"`
    IconUrlLarge     string                 `json:"icon_url_large"`
    IconDragUrl      string                 `json:"icon_drag_url"`
    Name             string                 `json:"name"`
    MarketHashName   string                 `json:"market_hash_name"`
    MarketName       string                 `json:"market_name"`
    NameColor        string                 `json:"name_color"`
    BGColor          string                 `json:"background_color"`
    Type             string                 `json:"type"`
    Tradable         string                 `json:"tradable"`
    Marketable       string                 `json:"marketable"`
    Commodity        string                 `json:"commodity"`
    TradeRestrict    string                 `json:"market_tradeable_restriction"`
    FraudWarnings    string                 `json:"fraudwarnings"`
    Descriptions     map[string]description `json:"descriptions"`
    OwnerDescription string                 `json:"owner_descriptions"`
    Tags             map[string]tag         `json:"tags"`
    ClassID          string                 `json:"classid"`
}

type description struct {
    Type    string `json:"type"`
    Value   string `json:"value"`
    Color   string `json:"color,omitempty"`
    AppData string `json:"appdata"`
}

type tag struct {
    InternalName string `json:"internal_name"`
    Name         string `json:"name"`
    Category     string `json:"category"`
    Color        string `json:"color,omitempty"`
    CategoryName string `json:"category_name"`
}

func main() {
    var ai AssetInfo
    json.Unmarshal([]byte(j), &ai)
    fmt.Printf("%+v
", ai.Result["720268538"].Tags)
}

The output of the Tags is map[0:{InternalName:CSGO_Type_WeaponCase Name:Container Category:Type Color: CategoryName:Type} 1:{InternalName:set_community_6 Name:The Chroma Collection Category:ItemSet Color: CategoryName:Collection} (...)

EDIT 2: After discussion in the comments I ended up at some code which is very similar to hobbs' answer. His might be better organized.

We need to use json.RawMessage since the true value for "success" can't go into the Numbered struct.

type AssetInfo struct {
    Result map[string]json.RawMessage
}

err := json.Unmarshal([]byte(j), &ai)
fmt.Println("outer error", err) // prints <nil>
for k, v := range ai.Result {
    // this is very similar to the example on the docs: https://golang.org/pkg/encoding/json/#RawMessage
    var dst interface{} 
    if k == "success" {
        dst = new(bool)
    } else {
        dst = new(Numbered)
    }
    err := json.Unmarshal(v, dst)
    fmt.Println("inner error", err) // prints <nil>
    if k == "success" {
        b := dst.(*bool)
        fmt.Printf("%t
", *b) // true
    } else {
        fmt.Printf("%+v
", dst) // the whole Numbered struct
    }
}