Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deserializing JSON that has an int as a key in C#

I am trying to deserialize this JSON

{
"39": {
    "category": "Miscellaneous",
    "country_whitelist": [],
    "name": "domain.com",
    "url_blacklist": [],
    "country_blacklist": [],
    "url_whitelist": [
        "domain.com"
    ],
    "deals": {
        "425215": {
            "status": "Ok",
            "type": "",
            "code": "CODE",
            "end_date": "2014-03-01 04:00:00",
            "title": "RandomTitle",
            "url": "http://domain.com/foo",
            "text": "Text Text Text",
            "long_title": "Longer Text"
        },
        "425216": {
            "status": "Ok",
            "type": "",
            "code": "CODE2",
            "end_date": "2014-03-01 04:00:00",
            "title": "RandomTitle2",
            "url": "http://domain.com/bar",
            "text": "Text Text Text",
            "long_title": "Longer Text"
        }
    },
    "88x31": "http://someimage/88x31.png",
    "subcategory": "Other"
 },
"40": {
    "category": "Miscellaneous",
    "country_whitelist": [],
    "name": "domain.com",
    "url_blacklist": [],
    "country_blacklist": [],
    "url_whitelist": [
        "domain.com"
    ],
    "products": {
        "425215": {
            "status": "Ok",
            "type": "",
            "code": "CODE",
            "end_date": "2014-03-01 04:00:00",
            "title": "RandomTitle",
            "url": "http://domain.com/foo",
            "text": "Text Text Text",
            "long_title": "Longer Text"
        },
        "425216": {
            "status": "Ok",
            "type": "",
            "code": "CODE2",
            "end_date": "2014-03-01 04:00:00",
            "title": "RandomTitle2",
            "url": "http://domain.com/bar",
            "text": "Text Text Text",
            "long_title": "Longer Text"
        }
    },
    "88x31": "http://someimage/88x31.png",
    "subcategory": "Other"
 }
}

I tried using Json.NET and I tried using ServiceStack's deserializer but I can't seem to get any type of representation for this JSON.

The main thing that is blocking me I believe is that the keys are Int but I don't have control on the JSON I receive.

This is the C# classes I have built

public class product
{
    public string status { get; set; }
    public string type { get; set; }
    public string code { get; set; }
    public string end_date { get; set; }
    public string title { get; set; }
    public string url { get; set; }
    public string text { get; set; }
    public string long_title { get; set; }
}

public class Merchant
{
    public string category { get; set; }
    public List<string> country_whitelist { get; set; }
    public string name { get; set; }
    public List<string> url_blacklist { get; set; }
    public List<string> country_blacklist { get; set; }
    public List<string> url_whitelist { get; set; }
    public List<product> products { get; set; }
    public string subcategory { get; set; }
}

public class Data
{
    public Dictionary<int, Merchant> MainMerchants { get; set; }
}

I prefer using ServiceStack but any other deserializer that works will be great

var data = client.Get(json);

like image 973
Afiku Avatar asked Feb 13 '14 11:02

Afiku


1 Answers

Getting your data types mapped correctly:

It is possible to deserialize your JSON. As you correctly identified you can deserialize to a Dictionary<int, Merchant>.

But you will need to change your definition of products in the Merchant class to be a Dictionary<int, Product>. It needs to be a dictionary here to handle your numeric key. List<Product> won't work.

Also to handle the 88x31 property you can use a DataMember(Name = '88x31') mapping to map it to something c# likes, like image88x31. Unfortunately this does mean your DTO properties become opt-in so you will then need to decorate all members. Add using System.Runtime.Serialization;

Once you make those changes such that:

// Note I capitalized Product
public class Product
{
    public string status { get; set; }
    public string type { get; set; }
    public string code { get; set; }
    public string end_date { get; set; }
    public string title { get; set; }
    public string url { get; set; }
    public string text { get; set; }
    public string long_title { get; set; }
}

/*
 * Use DataMember to map the keys starting with numbers to an alternative c# compatible name.
 * Unfortunately this requires properties to opt in to the data contract.
 */
[DataContract]
public class Merchant
{
    [DataMember]
    public string category { get; set; }

    [DataMember]
    public List<string> country_whitelist { get; set; }

    [DataMember]
    public string name { get; set; }

    [DataMember]
    public List<string> url_blacklist { get; set; }

    [DataMember]
    public List<string> country_blacklist { get; set; }

    [DataMember]
    public List<string> url_whitelist { get; set; }

    [DataMember]
    public Dictionary<int, Product>  products { get; set; }

    [DataMember]
    public string sub_category { get; set; }

    // This maps the 88x31 key to a c# appropriate name
    [DataMember(Name = "88x31")]
    public string image88x31 { get; set; }
}

Then you will be able to deserialize into Dictionary<int, Merchant> without any issues.

JsonSerializer.DeserializeFromString<Dictionary<int, Merchant>>("YOUR JSON STRING");

Using in a ServiceStack Service:

If you want to be able to send this request directly to a ServiceStack service, then you can use a RequestBinder to deserialize into this complex type. Given this service:

Request DTO:

[Route("/Merchants", "POST")]
public class MerchantsRequest
{
    public Dictionary<int, Merchant> MainMerchants { get; set; }
}

Simple Action Method:

public class MerchantsService : Service
{
    public void Post(MerchantsRequest request)
    {
        var merchant39 = request.MainMerchants.First(p=>p.Key == 39).Value;
        Console.WriteLine("Name: {0}\nImage: {1}\nProduct Count: {2}", merchant39.name, merchant39.image88x31, merchant39.products.Count);

        var merchant40 = request.MainMerchants.First(p=>p.Key == 40).Value;
        Console.WriteLine("Name: {0}\nImage: {1}\nProduct Count: {2}", merchant40.name, merchant40.image88x31, merchant40.products.Count);
    }
}

AppHost Configuration:

In your AppHost Configure method you would need to add a binder to the request type. i.e. typeof(MerchantsRequest) like so:

public override void Configure(Funq.Container container)
{
    Func<IRequest, object> merchantsRequestBinder = delegate(IRequest request) {
        var json = WebUtility.HtmlDecode( request.GetRawBody() );
        return new MerchantsRequest { MainMerchants = JsonSerializer.DeserializeFromString<Dictionary<int, Merchant>>(json) };
    };

    RequestBinders.Add(typeof(MerchantsRequest), merchantsRequestBinder);

    ...
}

This binder method will convert the json you are sending into a MerchantsRequest. Then you can use it like a regular ServiceStack request.

Full Source Code Here

A fully working example of console application, demonstrating the conversion of the complex JSON to a service request.


Note: I notice in your JSON that you have property deals on one object, and products on another, I assumed this was a typo, as you don't have a corresponding property on in the class for deals.

like image 83
Scott Avatar answered Oct 17 '22 10:10

Scott