简体   繁体   中英

Deserializing result of steam storefront api using RestSharp always returns null

I've been trying to use the steam storefront api http://store.steampowered.com/api/ .

The problem is that when I feed the result of the api to RestSharp, the data is always null.

So my question is how do I get the Rest call deserialized?

First, this is what you would be expecting when calling appdetails?appids=[appid]

{
  "3400": {
    "success": true,
    "data": {
      "type": "game",
      "name": "Hammer Heads Deluxe",
      "steam_appid": 3400,
      "required_age": 0,
      "is_free": false,
      "detailed_description": "",
      "about_the_game": "",
      "short_description": "",
      "supported_languages": "English",
      "header_image": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/3400\/header.jpg?t=1447350883",
      "website": null,
      "pc_requirements": {
        "minimum": "<p><strong>Minimum Requirements:<\/strong> Windows 98\/ME\/2000\/XP, 128 MB RAM, 500MHz or faster, DirectX: 7.0<\/p>"
      },
      "mac_requirements": [

      ],
      "linux_requirements": [

      ],
      "developers": [
        "PopCap Games, Inc."
      ],
      "publishers": [
        "PopCap Games, Inc."
      ],
      "demos": [
        {
          "appid": 3402,
          "description": ""
        }
      ],
      "price_overview": {
        "currency": "USD",
        "initial": 499,
        "final": 499,
        "discount_percent": 0
      },
      "packages": [
        135
      ],
      "package_groups": [
        {
          "name": "default",
          "title": "Buy Hammer Heads Deluxe",
          "description": "",
          "selection_text": "Select a purchase option",
          "save_text": "",
          "display_type": 0,
          "is_recurring_subscription": "false",
          "subs": [
            {
              "packageid": 135,
              "percent_savings_text": "",
              "percent_savings": 0,
              "option_text": "Hammer Heads Deluxe - $4.99",
              "option_description": "",
              "can_get_free_license": "0",
              "is_free_license": false,
              "price_in_cents_with_discount": 499
            }
          ]
        }
      ],
      "platforms": {
        "windows": true,
        "mac": false,
        "linux": false
      },
      "categories": [
        {
          "id": 2,
          "description": "Single-player"
        }
      ],
      "genres": [
        {
          "id": "4",
          "description": "Casual"
        }
      ],
      "screenshots": [
        {
          "id": 0,
          "path_thumbnail": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/3400\/0000000564.600x338.jpg?t=1447350883",
          "path_full": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/3400\/0000000564.1920x1080.jpg?t=1447350883"
        },
        {
          "id": 1,
          "path_thumbnail": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/3400\/0000000565.600x338.jpg?t=1447350883",
          "path_full": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/3400\/0000000565.1920x1080.jpg?t=1447350883"
        },
        {
          "id": 2,
          "path_thumbnail": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/3400\/0000000566.600x338.jpg?t=1447350883",
          "path_full": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/3400\/0000000566.1920x1080.jpg?t=1447350883"
        },
        {
          "id": 3,
          "path_thumbnail": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/3400\/0000000567.600x338.jpg?t=1447350883",
          "path_full": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/3400\/0000000567.1920x1080.jpg?t=1447350883"
        },
        {
          "id": 4,
          "path_thumbnail": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/3400\/0000000568.600x338.jpg?t=1447350883",
          "path_full": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/3400\/0000000568.1920x1080.jpg?t=1447350883"
        }
      ],
      "release_date": {
        "coming_soon": false,
        "date": "Aug 30, 2006"
      },
      "support_info": {
        "url": "",
        "email": ""
      },
      "background": "http:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/3400\/page_bg_generated_v6b.jpg?t=1447350883"
    }
  }
}

That is the json block returned for accessing appid 3400. Note how it's appid is located inside of file itself.

In order to generate classes for this, I made a formatted version of that based off the unofficial storefront api documentation . Then I used that to generate the container classes shown below.

public class Appid
{
    public bool success { get; set; }
    public Data data { get; set; }
}

public class Data
{
    public string type { get; set; }
    public string name { get; set; }
    public int steam_appid { get; set; }
    public int required_age { get; set; }
    public int[] dlc { get; set; }
    public string detailed_description { get; set; }
    public string about_the_game { get; set; }
    public Fullgame fullgame { get; set; }
    public string supported_languages { get; set; }
    public string header_image { get; set; }
    public string website { get; set; }
    public Pc_Requirements pc_requirements { get; set; }
    public Mac_Requirements mac_requirements { get; set; }
    public Linux_Requirements linux_requirements { get; set; }
    public string[] developers { get; set; }
    public Demos demos { get; set; }
    public Price_Overview price_overview { get; set; }
    public int[] packages { get; set; }
    public Package_Groups[] package_groups { get; set; }
    public Platforms platforms { get; set; }
    public Metacritic metacritic { get; set; }
    public Category[] categories { get; set; }
    public Screenshot[] screenshots { get; set; }
    public Movie[] movies { get; set; }
    public Recommendations recommendations { get; set; }
    public Achievements achievements { get; set; }
    public Release_Date release_date { get; set; }
}

public class Fullgame
{
    public int appid { get; set; }
    public string name { get; set; }
}

public class Pc_Requirements
{
    public string minimum { get; set; }
    public string recommended { get; set; }
}

public class Mac_Requirements
{
    public string minimum { get; set; }
    public string recommended { get; set; }
}

public class Linux_Requirements
{
    public string minimum { get; set; }
    public string recommended { get; set; }
}

public class Demos
{
    public int appid { get; set; }
    public string description { get; set; }
}

public class Price_Overview
{
    public string currency { get; set; }
    public int initial { get; set; }
    public int final { get; set; }
    public int discount_precent { get; set; }
}

public class Platforms
{
    public bool windows { get; set; }
    public bool mac { get; set; }
    public bool linux { get; set; }
}

public class Metacritic
{
    public int score { get; set; }
    public string url { get; set; }
}

public class Recommendations
{
    public int total { get; set; }
}

public class Achievements
{
    public int total { get; set; }
    public Highlighted[] highlighted { get; set; }
}

public class Highlighted
{
    public string name { get; set; }
    public string path { get; set; }
}

public class Release_Date
{
    public bool coming_soon { get; set; }
    public string date { get; set; }
}

public class Package_Groups
{
    public string name { get; set; }
    public string title { get; set; }
    public string description { get; set; }
    public string selection_text { get; set; }
    public string save_text { get; set; }
    public int display_type { get; set; }
    public string is_recurring_subscription { get; set; }
    public Sub[] subs { get; set; }
}

public class Sub
{
    public int packageid { get; set; }
    public string percent_savings_text { get; set; }
    public int percent_savings { get; set; }
    public string option_text { get; set; }
    public string option_description { get; set; }
}

public class Category
{
    public int id { get; set; }
    public string description { get; set; }
}

public class Screenshot
{
    public int id { get; set; }
    public string path_thumbnail { get; set; }
    public string path_full { get; set; }
}

public class Movie
{
    public int id { get; set; }
    public string name { get; set; }
    public string thumbnail { get; set; }
    public Webm webm { get; set; }
}

public class Webm
{
    public string _480 { get; set; }
    public string max { get; set; }
}

Now onto the code, the function takes in a long and should return a Data object. However, instead of deserializing, it returns a null. I have checked to make sure the json is actually getting to the code. It is coming in fine.

//Steam rest stuff always returns with one object.
//This it to keep me writing 10 classes that do essentially the same thing
//And before you ask, yes, I have tried doing this without this class.
//Still didn't work.
public class RootObject<T>
{
    public T response { get; set; }
}

public static Data GetGameData(long appid)
{
    //Only thing different about this function except for the fact that it's also static
    RestClient storeClient = new RestClient("http://store.steampowered.com/api/"); 

    RestRequest restReq = new RestRequest("appdetails/", Method.GET);
    restReq.AddParameter("appids", appid);
    restReq.RequestFormat = DataFormat.Json;

    restReq.OnBeforeDeserialization = resp =>
    {
        //This was done as an attempt to fix it.
        //It makes the appid's in the content into {"appId":{
        //EX: {"400":{ -> {"appId":{
        resp.Content = Regex.Replace(resp.Content, "{\"\\d+\":{", "{\"appId\":{");
    };

    IRestResponse<RootObject<Appid>> response = storeClient.Execute<RootObject<Appid>>(restReq);

    Appid trueRes = response.Data.response;
    return trueRes.data;
}

Quick note, the function was altered but only to work in a static context. The edit was noted in code.

Things I've tried

  • Explicitly specifying that the file is a json file.
  • Changing the appid class names to a common class name.
  • Using Json.Net as the deserializer.
  • Removing the "success": true item.
  • Using [DeserializeAs(Name = "appId")] in the appid class.
  • Using a non generic version of RootObject
  • Tried using Dictionary<string, Appid> instead of RootObject<Appid>

Edit: For some extra information I did some testing on the methods. I remade the object carrying classes using appid 4000 and put that into json to sharp converter. Then I wrote a small bit of code that tested if the data was null.

static void TestIfFetched(long appid)
{
    Console.Write($"{appid}\t:");
    Data data = GetGameData(appid);
    if (data == null)
    {
        Console.WriteLine("Failed to get data.");
    }
    else
    {
        Console.WriteLine("Data fetched successfully.");
    }
}

The result was this when testing 4000 and 400.

 4000   :Failed to get data.
 400    :Failed to get data.

So it seems even using data specifically designed for that game, it still fails to parse.

        ServicePointManager.Expect100Continue = true;
        ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
        RestClient storeClient = new RestClient("https://store.steampowered.com/api/");

        RestRequest restReq = new RestRequest("appdetails/", Method.GET);
        restReq.AddParameter("appids", appid);
        restReq.RequestFormat = DataFormat.Json;

        var response = storeClient.Execute<dynamic>(restReq);
        JObject jObject = JObject.Parse(response.Content);
        var details = jObject[appid].Value<JObject>().ToObject<Appid>();

The above worked for me. The JObject allows you to pass the appid as a parameter and then parse your object from there.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM