Deserializing JSON that has an int as a key in C# - 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);

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.

In your json string, for the products node, should it be this? as the type where it converted from is a List instead of dictionary?
I can get it work change it to following json string
"products": [{
"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"
},
{
"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"
}],

Related

Bind appsettings.json array section to generic class with generic list property

I have to read some values from appsettings.json using IOptionsSnapshot and return them through the API endpoint
"adConfig": [
{
"id": 3,
"adDef": [
{
"type": "number",
"key": "somekey",
"displayName": "Rules",
"values": [
0,
1
],
"regex": null
},
{
"type": "select",
"key": "anotherkey",
"displayName": "Some state",
"values": [
"some string value",
"another string value"
],
"regex": null
}
]
},
{
"id": 1
....
}
]
For this porpose I created 2 classes to use to bind the settings.
public class AdDataModel<T>
{
public string type { get; set; }
public string key{ get; set; }
public string displayName { get; set; }
public List<T> Values { get; set; }
public string regex { get; set; }
}
public class AdConfigurationModel
{
public int Id{ get; set; }
public List<AdDataModel<dynamic>> AdDefinition { get; set; }
}
In the Startup.cs I use this code to bind the settings to the classes:
services.Configure<List<AdConfigurationModel>>(Configuration.GetSection("adConfig"));
So far so good. I inject this into the Controller class but the problem is that AdDataModel.Values are aways of type object<string> ( which is not the case for the first element of adDef:Values subsection). Is there any way to achive Values to be from type int or string based on the values in appsettings.json?
Thanks!

c# nested json to object (current with json.net)

i am new to c# and json.net.
I have a json configuration file and try to parse it to objects. But how can i map the relations correctly in the objects?
Currently the property in project loop is null.
And can the objects map "automatically" without mapping each property name/value?
I can also change the json!
configuration.json:
{
"debug": true,
"log": "database",
"projects": [
{
"name": "Name 1",
"showInfo": false,
"ranges": [
[
5,
6
],
[
9,
10
],
[
15,
20
]
],
"additional": [
{
"name": "subName 1",
"parameter": "ID"
},
{
"name": "subName 2",
"parameter": "ID2"
}
]
},
{
"name": "Name 2",
"showInfo": false,
"ranges": [
[
99,
100
]
]
},
{
"name": "Name 3",
"showInfo": false,
"ranges": [
[
44,
45
]
]
},
{
"name": "Name 4",
"showInfo": false,
"ranges": [
[
12,
14
]
],
"additional": [
{
"name": "subName xy",
"parameter": "ID"
}
]
}
]
}
my try to parse:
Configuration configuration = new Configuration();
JObject jObject = JObject.Parse(File.ReadAllText(filePath));
if (jObject.ContainsKey("debug"))
{
configuration.Debug = (bool) jObject["debug"];
}
if (jObject.ContainsKey("log"))
{
configuration.Log = (string) jObject["log"];
}
//loop projects
JToken projects = jObject["projects"];
foreach (JToken child in projects.Children())
{
var property = child as JProperty;
if (property != null)
{
var test = property.Name;
var test2 = property.Value;
}
}
the objects:
public class Configuration
{
public bool Debug { get; set; } = false;
public string Log { get; set; }
// this is propably wrong
public Dictionary<string, Dictionary<string, Project>> Projects { get; set; }
}
public class Project
{
public string name { get; set; }
public bool showInfo{ get; set; }
// wrong?
public int[,] ranges { get; set; }
// wrong?
public Additional[] Additional{ get; set; }
}
public class Additional
{
public string Name{ get; set; }
public string Parameter { get; set; }
}
You don't need a JObject at all json files can be desterilized to objects it's much more efficient because you don't create unneeded objects.
You Config class is just wrong
public class Configuration
{
public bool Debug { get; set; }
public string Log { get; set; }
public Project[] Projects { get; set; }
}
public class Project
{
public string Name { get; set; }
public bool ShowInfo { get; set; }
public int[][] Ranges { get; set; }
public Additional[] Additional { get; set; }
}
public class Additional
{
public string Name { get; set; }
public string Parameter { get; set; }
}`
Should look like this.
And then use. JsonConvert.DeserializeObject<Configuration>(json); To get the object.
If you have Visual studio it has this cool feature called paste special where you can just past your json and it will create a proper class for deserialization. It's under Edit-> Paste special-> Paste json as class

C# Web API Nested JSON from Stored Procedure in MSSQL

Overview:
I'm building an API using ASP.NET Web API 2. I'm building Stored Procedures in SQL and linking these in the API to serve data.
I know that SQL can return JSON using FOR JSON AUTO, for example. But, I don't think it's the best place to configure the data and out put JSON. So I'm assuming there must be a way to achieve nested JSON in the API.
This is what I want to achieve:
{
"Apps": [
{
"ItemID": "1",
"Path": "/AppReport",
"Name": "AppReport",
"Reports": [
{
"Reports_ItemID": "11",
"Reports_Path": "/AppReport/SubReport",
"Reports_Name": "SubReport"
},
{
"Reports_ItemID": "12",
"Reports_Path": "/AppReport/SubReport2",
"Reports_Name": "SubReport2"
}
]
},
{
"ItemID": "2",
"Path": "/AppReport2",
"Name": "AppReport2",
"Reports": [
{
"Reports_ItemID": "22",
"Reports_Path": "/AppReport/SubReport",
"Reports_Name": "SubReport"
}
]
}
]
}
At the moment it's coming out as a flat Array of Objects
{
"Apps": [
{
"ItemID": "1",
"Path": "/AppReport",
"Name": "AppReport",
"Reports_ItemID": "11",
"Reports_Path": "/AppReport/SubReport",
"Reports_Name": "SubReport"
},
{
"ItemID": "1",
"Path": "/AppReport",
"Name": "AppReport",
"Reports_ItemID": "12",
"Reports_Path": "/AppReport/SubReport2",
"Reports_Name": "SubReport2"
},
...
]
}
I have a class of the Stored Procedure in the API that looks like this:
namespace API.Stored_Procedures
{
public partial class SP_GetApps
{
public System.Guid ItemID { get; set; }
public string Path { get; set; }
public string Name { get; set; }
public System.Guid Report_ItemID { get; set; }
public string Report_Path { get; set; }
public string Report_Name { get; set; }
}
}
My Controller looks like this:
[HttpGet]
[Route("{uid}", Name ="getReportApps")]
[ResponseType(typeof(SP_GetApps_Result))]
public IHttpActionResult SP_GetApps(string uid)
{
var res = db.Database.SqlQuery<SP_GetApps_Result>
("SP_GetApps {0}", uid);
return Ok(res);
}
In order to achieve the desired JSON Class should look like
public class App
{
public string ItemID { get; set; }
public string Path { get; set; }
public string Name { get; set; }
public List<Report> Reports { get; set; }
}
public class Report
{
public string Reports_ItemID { get; set; }
public string Reports_Path { get; set; }
public string Reports_Name { get; set; }
}

How do I configure services when I bind an array of objects?

I have a json string attached to the VCAP_SERVICES environment variable that looks like this:
{
"redislabs": [
{
"credentials": {
"host": "redis-1756.pcfredissb2.com",
"ip_list": [
"10.999.46.999"
],
"password": "cz(2u",
"port": 1756
},
"syslog_drain_url": null,
"volume_mounts": [],
"label": "redislabs",
"provider": null,
"plan": "simple-redis",
"name": "sdsredis2",
"tags": [
"redislabs"
]
},
{
"credentials": {
"host": "redis-13610.pcfredis.com",
"ip_list": [
"10.999.46.9999"
],
"password": "n-C*",
"port": 13610
},
"syslog_drain_url": null,
"volume_mounts": [],
"label": "redislabs",
"provider": null,
"plan": "simple-redis",
"name": "sdsredis",
"tags": [
"redislabs"
]
}
]
}
In ConfigureServices in Startup.cs, when I run Configuration.GetSection("redislabs").AsEnumerable() I get something that looks like this:
I have a few options classes that looks like this:
public class RedisLabs
{
public RedisLab[] redislabs { get; set; }
}
public class RedisLab
{
public Credentials credentials { get; set; }
public object syslog_drain_url { get; set; }
public object[] volume_mounts { get; set; }
public string label { get; set; }
public object provider { get; set; }
public string plan { get; set; }
public string name { get; set; }
public string[] tags { get; set; }
}
public class Credentials
{
public string host { get; set; }
public string[] ip_list { get; set; }
public string password { get; set; }
public int port { get; set; }
}
My question is how the heck do I perform the binding on something like this? Is binding an array of objects even possible?
I've tried:
var redislabs = new List<RedisLab>();
Configuration.GetSection("redislabs").Bind(redislabs);
and
services.Configure<RedisLabs>(Configuration);
and a few other methods. Nothing seems to work.
Halp, plz.
Given your json file and classes this should work (binding top level section to instance of a class that that holds the array):
var redisLabs = new RedisLabs();
Configuration.Bind(redisLabs);
This also works:
services.Configure<RedisLabs>(Configuration);
...
var rlOptions = app.ApplicationServices.GetRequiredService<IOptions<RedisLabs>>();
var redisLabs = rlOptions.Value;

How to convert JSON to C# classes?

I have a complex JSON object that I want represent as C# class. I have a head start on the parent class called "Form", but how can I represent a collection for different types (see the "elements" object below)?
Here is the JSON object:
{
"action": "index.html",
"method": "post",
"elements":
[
{
"type": "fieldset",
"caption": "User information",
"elements":
[
{
"name": "email",
"caption": "Email address",
"type": "text",
"placeholder": "E.g. user#example.com",
"validate":
{
"email": true
}
},
{
"name": "password",
"caption": "Password",
"type": "password",
"id": "registration-password",
"validate":
{
"required": true,
"minlength": 5,
"messages":
{
"required": "Please enter a password",
"minlength": "At least {0} characters long"
}
}
},
{
"name": "password-repeat",
"caption": "Repeat password",
"type": "password",
"validate":
{
"equalTo": "#registration-password",
"messages":
{
"equalTo": "Please repeat your password"
}
}
},
{
"type": "radiobuttons",
"caption": "Sex",
"name": "sex",
"class": "labellist",
"options":
{
"f": "Female",
"m": "Male"
}
}
]
]
}
The class I have start looks like this:
public class Form
{
public Guid id
{
get;
set;
}
public string action
{
get;
set;
}
public string method
{
get;
set;
}
public ??? elements
{
get;
set;
}
public Form()
{
}
}
How do I handle the "elements" property to get the desired JSON output?
I am using WCF 4.0 with these atributes in the web.config: automaticFormatSelectionEnabled="false", defaultOutgoingResponseFormat="Json". Any help or ideas would be greatly appreciated.
If you don't have the liberty of using dynamic types from .NET 4 or would like to leverage the benefits that static typing provide, the JSON Class Generator project on codeplex will generate c# classes given a json input string. (shameless plug) I've also taken code from this project and slapped a web UI on it.
Wow. Fascinating question. Maybe use ExpandoObject / dynamic?
http://msdn.microsoft.com/en-us/library/system.dynamic.expandoobject.aspx
http://blogs.msdn.com/b/csharpfaq/archive/2009/10/01/dynamic-in-c-4-0-introducing-the-expandoobject.aspx?PageIndex=4
Or anonymous types I think are serializable with the built-in .NET JSON serializer.
You do not need to try and create the class structure manually.
Sometimes it is rather frustrating too. :)
There is a visual studio command you can use (I think vs2015 and later):
On a new class file click Menu => Edit => Paste Special
Select "Paste JSON as Classes"
Now specifically in your JSON there is an error, you are missing the closing curly-brace of first "element" object.
Below is the corrected JSON:
{
"action": "index.html",
"method": "post",
"elements": [
{
"type": "fieldset",
"caption": "User information",
"elements": [
{
"name": "email",
"caption": "Email address",
"type": "text",
"placeholder": "E.g. user#example.com",
"validate": {
"email": true
}
},
{
"name": "password",
"caption": "Password",
"type": "password",
"id": "registration-password",
"validate": {
"required": true,
"minlength": 5,
"messages": {
"required": "Please enter a password",
"minlength": "At least {0} characters long"
}
}
},
{
"name": "password-repeat",
"caption": "Repeat password",
"type": "password",
"validate": {
"equalTo": "#registration-password",
"messages": {
"equalTo": "Please repeat your password"
}
}
},
{
"type": "radiobuttons",
"caption": "Sex",
"name": "sex",
"class": "labellist",
"options": {
"f": "Female",
"m": "Male"
}
}
]
}
]
}
And the corresponding Classes:
public class Rootobject
{
public string action { get; set; }
public string method { get; set; }
public Element[] elements { get; set; }
}
public class Element
{
public string type { get; set; }
public string caption { get; set; }
public Element1[] elements { get; set; }
}
public class Element1
{
public string name { get; set; }
public string caption { get; set; }
public string type { get; set; }
public string placeholder { get; set; }
public Validate validate { get; set; }
public string id { get; set; }
public string _class { get; set; }
public Options options { get; set; }
}
public class Validate
{
public bool email { get; set; }
public bool required { get; set; }
public int minlength { get; set; }
public Messages messages { get; set; }
public string equalTo { get; set; }
}
public class Messages
{
public string required { get; set; }
public string minlength { get; set; }
public string equalTo { get; set; }
}
public class Options
{
public string f { get; set; }
public string m { get; set; }
}
If you just want to make sure all this unknown data gets deserialized and can be reserialized at some point in the future, I suggest the usage of IExtensibleDataObject.
Here are some samples to get you started. Hope this helps! (If you already knew this and were looking for something different...let me know!)
Forward-Compatible Data Contracts
Data Contract Versioning
Useful clarifying thread on the topic at MSDN forums

Categories