1

I am making a program which needs to parse a JSON response. Most of the data in the response is useless but I need to do a few things with it.

  1. Be able to retrieve the search for the id from the rgInventory section, then get the corresponding classid
  2. Get the market_name for the object with this classid

JSON Response (snippet, it continues on with that format):

{
   "success":true,
   "rgInventory":{
      "1482735510":{
         "id":"1482735510",
         "classid":"469449975",
         "instanceid":"0",
         "amount":"1",
         "pos":1
      },
      "1468698711":{
         "id":"1468698711",
         "classid":"619638799",
         "instanceid":"0",
         "amount":"1",
         "pos":2
      },
   },
   "rgCurrency":[
   ],
   "rgDescriptions":{
      "469449975_0":{
         "appid":"730",
         "classid":"469449975",
         "instanceid":"0",
         "icon_url":"fWFc82js0fmoRAP-qOIPu5THSWqfSmTELLqcUywGkijVjZYMUrsm1j-9xgEObwgfEh_nvjlWhNzZCveCDfIBj98xqodQ2CZknz5oM7bgZghmfzvDE61HY-Yy_QbpNis77893GtbmoLpffljq4tCXNLN9ZY0fSZPVCaWPZQ_5v0tshKIJK5KBqSjs2i73ejBdAx_EB8I",
         "icon_url_large":"fWFc82js0fmoRAP-qOIPu5THSWqfSmTELLqcUywGkijVjZYMUrsm1j-9xgEObwgfEh_nvjlWhNzZCveCDfIBj98xqodQ2CZknz5oM7bgZghmfzvDE61HY-Yy_QbpNis77893a9u35bwDZ13vs9PPNOQpZoodGMOBD6PVMFr4uRgxg6dZepXdpCm72SrhM2wJXBD1ujVT-Ntzxu8",
         "icon_drag_url":"",
         "name":"SG 553 | Army Sheen",
         "market_hash_name":"SG 553 | Army Sheen (Factory New)",
         "market_name":"SG 553 | Army Sheen (Factory New)",
         "name_color":"D2D2D2",
         "background_color":"",
         "type":"Consumer Grade Rifle",
         "tradable":1,
         "marketable":1,
         "commodity":0,
         "descriptions":[
            {
               "type":"html",
               "value":"Exterior: Factory New"
            },
            {
               "type":"html",
               "value":"The Bank Collection",
               "color":"9da1a9",
               "app_data":{
                  "def_index":"65535",
                  "is_itemset_name":1
               }
            },
         ],
         "actions":[
            {
               "name":"Inspect in Game...",
               "link":"steam:\/\/rungame\/730\/76561202255233023\/+csgo_econ_action_preview%20S%owner_steamid%A%assetid%D2486209296654018845"
            }
         ],
         "market_actions":[
            {
               "name":"Inspect in Game...",
               "link":"steam:\/\/rungame\/730\/76561202255233023\/+csgo_econ_action_preview%20M%listingid%A%assetid%D2486209296654018845"
            }
         ],
         "tags":[
            {
               "internal_name":"CSGO_Type_Rifle",
               "name":"Rifle",
               "category":"Type",
               "category_name":"Type"
            },
            {
               "internal_name":"weapon_sg556",
               "name":"SG 553",
               "category":"Weapon",
               "category_name":"Weapon"
            },
         ]
      }
   }
}

Full JSON response: http://steamcommunity.com/id/Mambocsgoshack/inventory/json/730/2/

I believe because the identifiers keep changing for each item in the inventory (i.e. 469449975_0 to 619638799_0) I would have to deserialize this to a dictionary.

Here is my code thus far:

namespace SteamTrade
{
    public class CSGOInventory
    {
        public static CSGOInventory FetchInventory(string steamId)
        {
            WebClient client = new WebClient();
            var url = "http://steamcommunity.com/profiles/" + steamId + "/inventory/json/730/2/";
            string response =  client.DownloadString(url);
            Dictionary<string, Item> result = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, Item>>(response);
            return new CSGOInventory(result);
        }

        public Item[] Items { get; set; }
        public bool IsPrivate { get; private set; }
        public bool IsGood { get; private set; }

        protected CSGOInventory(Dictionary<string, Item> apiInventory)
        {
            for (int i = 0; i < apiInventory.Count; i++)
            {
                Items[i] = apiInventory.Values.ElementAt(i);
            }
        }

        /*public Item GetItem(int id)
        {
            return (Items == null ? null : Items.FirstOrDefault(item => item.instanceid == id));
        }

        public List<Item> GetItemsByDefindex(int defindex)
        {
            return Items.Where(item => item.def_index == defindex).ToList();
        }*/

        public class Item
        {
            public string AppId = "730";
            public string ContextId = "2";

            [JsonProperty("instanceid")]
            public string instanceid { get; set; }

            [JsonProperty("market_name")]
            public string market_name { get; set; }

            [JsonProperty("def_index")]
            public string def_index { get; set; }

        }

        protected class InventoryResult
        {
            public Item[] items { get; set; }
        }

        protected class InventoryResponse
        {
            public InventoryResult result;
        }

    }
}

I believe I am adding the dictionary items to the Items array completely wrong but cannot figure out the correct solution.

However, the error currently is:

Error converting value True to type 'SteamTrade.CSGOInventory+Item , Path 'success', line 1, position 15.

I sort of understand what this means but do not know how to work around it. I didn't think I had to define every property that the JSON returns within my object, but I could well be wrong. Either way, since the format of the JSON changes from the rgInventory to rgDescriptions sections changes I do not know how to address this. Could anyone explain how to do this?

UPDATE:

My method to retrieve instanceid from market_name is as follows:

public string getInstanceIdFromMarketName(string name)
        {
            var classIdToId = inventory.rgInventory.ToLookup(pair => pair.Value.classid, pair => pair.Key);
            var marketNameToId = inventory.rgDescriptions
                .SelectMany(pair => classIdToId[pair.Value.classid].Select(id => new KeyValuePair<string, string>(pair.Value.market_name, id)))
                .ToLookup(pair => pair.Key, pair => pair.Value);
            if (marketNameToId[name].First() != null)
            {
                string idForMarket = marketNameToId[name].FirstOrDefault();
                return idForMarket;
            }
            else
            {
                return null;
            }
        }

This returns an error saying there are no items in the sequence.

Jed Boffey
  • 37
  • 1
  • 8

3 Answers3

2

The JSON you posted in your question is invalid, according to http://jsonformatter.curiousconcept.com/. However, the JSON at your link is OK, so lets go with that.

The information you want can be modeled as a class containing two dictionaries:

public class rgInventoryItem
{
    public string id { get; set; }
    public string classid { get; set; }
}

public class rgDescription
{
    public string classid { get; set; }
    public string market_name { get; set; }
}

public class InventoryResponse
{
    public Dictionary<string, rgInventoryItem> rgInventory { get; set; }

    public Dictionary<string, rgDescription> rgDescriptions { get; set; }
}

Then, to load from a JSON string, use:

    var response = JsonConvert.DeserializeObject<InventoryResponse>(json);

However, the rgDescriptions dictionary is not indexed on classid directly, instead the keys are are related to classid somehow, for instance

  "469449975_0":{
     "classid":"469449975",
     "market_name":"SG 553 | Army Sheen (Factory New)",

To create a lookup for market name from classid, you can do

    var classidToDescription = response.rgDescriptions.ToLookup(pair => pair.Value.classid);

This will find all rgDescription classes for a given classid.

If you are sure there is only one rgDescription for a given classid, you can do:

    var classidToDescriptionDictionary = response.rgDescriptions.ToDictionary(pair => pair.Value.classid);

Be aware this will throw an ArgumentException with the message "An item with the same key has already been added" if more than one description has the same class id.

update

In order to go from market_name to id, you need to invert the dictionaries and create reverse lookup tables. Thus:

  1. If you need all market names in the response, do:

        var marketNames = response.rgDescriptions.Values.Select(d => d.market_name);
    
  2. If you need all ids in the response, do:

        var ids = response.rgInventory.Keys;
    
  3. to map from market_name to id, first create the reverse lookup:

        var classIdToId = response.rgInventory.ToLookup(pair => pair.Value.classid, pair => pair.Key);
        var marketNameToId = response.rgDescriptions
            .SelectMany(pair => classIdToId[pair.Value.classid].Select(id => new KeyValuePair<string, string>(pair.Value.market_name, id)))
            .ToLookup(pair => pair.Key, pair => pair.Value);
    

    To get all ids that refers to a given market name, do:

        var idsForMarket = marketNameToId[name].ToList();
    

    To get the first id that refers to a given market name, do:

        var firstIdForMarket = marketNameToId[name].FirstOrDefault();
    

Update 2

Here are modified versions of your classes:

public class rgInventoryItem
{
    public string id { get; set; }
    public string classid { get; set; }
}

public class rgDescription
{
    public string classid { get; set; }
    public string market_name { get; set; }
}

public class CSGOInventory
{
    public static CSGOInventory FetchInventory(string steamId)
    {
        var url = "http://steamcommunity.com/profiles/" + steamId + "/inventory/json/730/2/";
        return FetchInventoryFromUrl(new Uri(url));
    }

    public static CSGOInventory FetchInventoryFromUrl(Uri url)
    {
        using (WebClient client = new WebClient())
        {
            string response = client.DownloadString(url);
            var inventory = JsonConvert.DeserializeObject<InventoryResponse>(response);
            return new CSGOInventory(inventory);
        }
    }

    readonly InventoryResponse inventory;
    readonly ILookup<string, string> classIdToId;
    readonly ILookup<string, string> marketNameToId;

    CSGOInventory(InventoryResponse inventory)
    {
        if (inventory == null)
            throw new ArgumentNullException();

        this.inventory = inventory;

        this.classIdToId = inventory.rgInventory.ToLookup(pair => pair.Value.classid, pair => pair.Key);
        this.marketNameToId = inventory.rgDescriptions
            .SelectMany(pair => classIdToId[pair.Value.classid].Select(id => new KeyValuePair<string, string>(pair.Value.market_name, id)))
            .ToLookup(pair => pair.Key, pair => pair.Value);
    }


    public IDictionary<string, rgInventoryItem> InventoryItems { get { return this.inventory == null ? null : this.inventory.rgInventory; } }

    public IDictionary<string, rgDescription> InventoryDescriptions { get { return this.inventory == null ? null : this.inventory.rgDescriptions; } }

    public IEnumerable<string> MarketNames { get { return InventoryDescriptions == null ? null : InventoryDescriptions.Values.Select(d => d.market_name); } }

    public IEnumerable<string> InventoryIds { get { return InventoryItems == null ? null : InventoryItems.Keys; } }

    public string getInstanceIdFromMarketName(string name)
    {
        return marketNameToId[name].FirstOrDefault();
    }

    public IEnumerable<string> getInstanceIdsFromMarketName(string name)
    {
        return marketNameToId[name];
    }

    class InventoryResponse
    {
        public Dictionary<string, rgInventoryItem> rgInventory { get; set; }

        public Dictionary<string, rgDescription> rgDescriptions { get; set; }
    }
}

Using this, the following test class:

public static class TestClass
{
    public static void Test()
    {
        //string url = @"d:\temp\question28328432.json";
        string url = @"http://steamcommunity.com/id/Mambocsgoshack/inventory/json/730/2/";
        var inventory = CSGOInventory.FetchInventoryFromUrl(new Uri(url));
        foreach (var market in inventory.MarketNames)
        {
            Console.WriteLine(string.Format("    Market {0,-50}: id {1}", market, inventory.getInstanceIdFromMarketName(market)));
        }
    }
}

gives the output

Market SG 553 | Army Sheen (Factory New)                 : id 1482735510
Market Offer | Music Kit | Noisia, Sharpened             : id 1468698711
Market Offer | Sticker | Bomb Squad (Foil)               : id 1468698710
Market Offer | Sticker | Dinked                          : id 1468698709
Market Offer | Sticker | Kawaii Killer CT                : id 1468698708
Market Operation Breakout Weapon Case                    : id 1462270322
Market Operation Vanguard Weapon Case                    : id 1459818809
Market M4A4 | Howl (Minimal Wear)                        : id 1450750270
Market Operation Phoenix Weapon Case                     : id 1391297747
Market Negev | Army Sheen (Minimal Wear)                 : id 1370560151
Market Huntsman Weapon Case                              : id 1305163655
Market Tec-9 | Army Mesh (Minimal Wear)                  : id 1304896559
Market Galil AR | Cerberus (Well-Worn)                   : id 1214784536
Market StatTrakT Tec-9 | Sandstorm (Field-Tested)        : id 1201208194
Market G3SG1 | Contractor (Field-Tested)                 : id 1189828757
Market Campaign Vanguard                                 : id 1103736871
Market Campaign Weapons Specialist                       : id 1103736870
Market Operation Vanguard Challenge Coin                 : id 1103736869
Market StatTrakT XM1014 | Red Python (Field-Tested)      : id 957595359
Market StatTrakT CZ75-Auto | Hexane (Field-Tested)       : id 814442137
Market Negev | Army Sheen (Factory New)                  : id 623936007
Market SSG 08 | Sand Dune (Well-Worn)                    : id 616381102
Market Silver Operation Breakout Coin                    : id 612997861
Market UMP-45 | Scorched (Field-Tested)                  : id 603041123
dbc
  • 104,963
  • 20
  • 228
  • 340
  • So given the market name, I could get the classid doing this in reverse effectively? – Jed Boffey Feb 04 '15 at 19:16
  • You mean you want to know how I created the data model, how to use work with the field `rgInventory`, or how to use the return value from `response.rgDescriptions.ToLookup`? – dbc Feb 04 '15 at 19:20
  • bad question on my end. I'm needing to create a method that can get the "id" within the rgInventory section from the market_name – Jed Boffey Feb 04 '15 at 19:21
  • So you want to go from `market_name` to `id`, then? – dbc Feb 04 '15 at 19:22
  • @JedBoffey - updated answer. Is that what you need? – dbc Feb 04 '15 at 19:49
  • @JedBoffey - 1) Are your questions about dictionaries resolved? 2) The URL in your update, `"http://steamcommunity.com/profiles/" + steamId + "/inventory/json/730/2/"`, does not match the URL in your original question, "http://steamcommunity.com/id/Mambocsgoshack/inventory/json/730/2/". We don't even know the value of `steamId`. Can you create a [minimal, complete and verifiable](http://stackoverflow.com/help/mcve) example of your new problem? – dbc Feb 04 '15 at 20:24
  • `First()` throws an exception on an empty sequence, `FirstOrDefault()` does not and just returns `null`. You need to show your entire class for me to comment further. For instance, you should probably create the lookup tables in the class constructor, after the data is retrieved from the server. – dbc Feb 04 '15 at 20:34
  • @JedBoffey - answer updated with modified versions of your classes. This seems to be what you want. – dbc Feb 04 '15 at 23:12
1

What you are trying to do really is query your JSON. For that, you could deserialize it using a dynamic object like DixonD proposed, and then iterate through your dynamic object to find the information you need.

An other simpler and cleaner solution is to query your JSON, using a library like jsonpath which is the equivalent of XPath for JSON. An example of how to do such thing is available here.

Community
  • 1
  • 1
slvnperron
  • 1,323
  • 10
  • 13
0

You are trying to deserialize it to Dictionary<string, Item> which obviously fails since you have elements that cannot be deserialized to Item and it fails on the first one of them which is "success": true

You have a couple ways how you can proceed.

  1. var result = (dynamic)Newtonsoft.Json.JsonConvert.DeserializeObject(response);

    In such a case you can process the result as a dynamic object.

  2. Define and use the class which corresponds to the format of json you are trying to deserialize.

DixonD
  • 6,557
  • 5
  • 31
  • 52
  • say I use the first method you suggested, how do I then go onto obtain the functionality that I need? (stated at the top of my post) – Jed Boffey Feb 04 '15 at 18:54