How to receive JSON data into Web API ApiController method? - c#

I'm writing a Web API ApiController with several PUT methods that receive JSON data. The JSON is not deterministic and hence cannot be hard-mapped to a custom C# object, but needs to be received as Dictionaries/Sequences (Maps/Lists).
I have tried using an IDictionary for the data parm of the PUT method in the controller, and this sort of works -- the data appears to be mapped from JSON to the dictionary. However, it's necessary to declare the dictionary as <String,Object>, and there's no clear way to then retrieve the Object values as their appropriate types. (I've found a few suggested kluges in my searching, but they are just that.)
There is also a System.Json.JsonObject type which I finally managed to get loaded via NuGet, but when I use that the system does not appear to know how to map the data.
How is this typically done? How do you implement an ApiController method that receives generic JSON?
I can see three basic approaches:
Somehow make Dictionary/Sequence work with Object or some such.
Make something like System.Json.JsonObject work, perhaps by swizzling the routing info.
Receive the JSON as a byte array and then parse explicitly using one of the C# JSON toolkits available.
(As to how dynamic the data is, JSON objects may have missing entries or extraneous entries, and in some cases a particular entry may be represented as either a single JSON value or a JSON array of values. (Where "value" is JSON array, object, string, number, Boolean, or null.) In general, except for the array/not array ambiguity, the relation between keys and value types is known.)
(But I should note that this is a large project and I'll be receiving JSON strings from several other components by other authors. Being able to examine the received type and assert that it's as expected would be quite useful, and may even be necessary from a security standpoint.)
(I should add that I'm a relative novice with C# -- have only been working with it for about 6 months.)

You've got to know what kind of data you're expecting, but I have had success doing this in the past using dynamic typing.
Something like this:
[Test]
public void JsonTester()
{
string json = "{ 'fruit':'banana', 'color':'yellow' }";
dynamic data = JsonConvert.DeserializeObject(json);
string fruit = data["fruit"];
string color = data["color"];
Assert.That(fruit == "banana");
Assert.That(color == "yellow");
}
Edit:
You either need to know the type you want to deserialize to beforehand - in which case you can deserialize it to that type immediately.
Or you can deserialize it to a dynamic type, and then convert it to your static type once you know what you want to do with it.
using Newtonsoft.Json;
using NUnit.Framework;
public class DTO
{
public string Field1;
public int Field2;
}
public class JsonDeserializationTests
{
[Test]
public void JsonCanBeDeserializedToDTO()
{
string json = "{ 'Field1':'some text', 'Field2':45 }";
var data = JsonConvert.DeserializeObject<DTO>(json);
Assert.That(data.Field1 == "some text");
Assert.That(data.Field2 == 45);
}
[Test]
public void JsonCanBeDeserializedToDynamic_AndConvertedToDTO()
{
string json = "{ 'Field1':'some text', 'Field2':45 }";
var dynamicData = JsonConvert.DeserializeObject<dynamic>(json);
var data = new DTO { Field1 = dynamicData["Field1"], Field2 = dynamicData["Field2"] };
Assert.That(data.Field1 == "some text");
Assert.That(data.Field2 == 45);
}
}

Related

RestSharp v107 Parameter question adding nested JSON / objects

I'm attempting to update my RestSharp calls to v107 and noticed a slight difference in how parameters are added. What I have working in 106 doesn't appear to work in 107.
What I have is that I'm adding an object with nested objects to a POST request. My object looks like this:
class CallParameters
{
public CallResources Resources {get;set;}
public CallFilters Filters {get;set;}
}
class CallResources
{
public bool IncludeOrgData {get;set;}
public bool IncludeDemoData {get;set}
}
class CallFilters
{
public string LastModified {get;set}
public List<string> Users {get;set;}
}
I know I can serialize that (once I set the fields) to look like:
{"Resources" : {"IncludeOrgData" : true, "IncludeDemoData" : true}, "Filters" : { "LastModifed" : "1/1/21", "Users" : ["User1", User2", "User3"]}}
With 106, I was able to create my rest call by serializing my object class to a string and adding it via AddJsonBody like this
CallParameters NewCallParameters = new CallParameters();
...Set the fields within the class...
string JsonBody = JsonConvert.SerializeObject(NewCallParameters);
var request = new RestRequest { Method = Method.POST };
var client = new RestClient();
var response = client.Execute(request.AddJsonBody(JsonBody));
Worked great. Passed over all the parameters right
In 107 I know it's slightly different. Using the same serialize and AddJsonObject, I show a single parameter is added with no name, and the value of the whole serialized object. This causes my call to return
The request is invalid - An error has occurred
If I use 'AddObject' to add the entire class CallParamters class as a whole, when I trace through things and look at my request parameters collection, I do see two parameters listed. They are named correctly ('Resources' and 'Filters') but the values are 'MyApp.CallResources' and 'MyApp.CallFilters'. It's like they are not serializing when adding. This returns:
At least one additional resource is required: IncludeOrgData, IncludeDemoData
If I add the objects separately as 'CallResourses' and 'CallFilters', I wind up with four parameters names for all the fields within. Value is correct aside from the Users List, which just shows 'System.Collections.Generic.List[System.String]'. Also having four parameters doesn't seem right either and the call also fails with invalid parameters. This returns
The request is invalid - Resources field is required
I have experimented with 'AddParamter' some. If I add the whole class as a parameter I'm not sure what to name it. Also the call fails since it has no name. If I add them separately like this:
string MyResources = JsonSerializer.Serialize(NewCallParameters.Resources);
string MyFilters = JsonSerializer.Serialize(NewCallParameters.Filters);
request.AddParamter("Resources", MyResources );
request.AddParamter("Filters", MyFilters);
I do get two parameters named correctly. The values look good and show all the entries. The return result though from the Rest service I'm calling states
At least one additional resource is required: IncludeOrgData, IncludeDemoData'
So it seems it's not seeing the values in the 'Resources' parameter. Do I need to add each as a separate parameter? How would I add nested parameters that way? I feel like I'm close but not getting something.
It helps to read the documentation. RestSharp has embedded serialization. Don't serialize your object when using AddJsonBody. As you need Pascal-case fields, you'd need to either change JSON options for the default serializer or use the RestSharp.Serializers.NewtonsoftJson package.
If you prefer to add a pre-serialized string, use AddStringBody

Parse JSON using Newtonsoft.Json.Linq.JToken.SelectToken

I am trying to parse JSON to obtain the value of the VIN number in the following snippet. The SelectToken call is returning a null value.
I tried using the same token to test on the following websites and it works there
http://jsonpath.com
https://jsonpath.curiousconcept.com
I am unable to identify what is going on here. Is it because this token is not supported in Newtownsoft.Json, or is it something else
[Test]
public void Test()
{
string responseContent =
"{\"GetAdvisorWipDetailPageResponse\":{\"AdvisorDetailPage\":{\"TaxLabel\":\"VAT\",\"Currency\":{\"Code\":\"UKL\",\"Description\":\"Sterling\",\"Symbol\":\"£\",\"Precision\":\"2\"},\"LastMileage\":\"0\",\"NetAmount\":\"52.73\",\"VATAmount\":\"10.55\",\"TotalAmount\":\"63.28\",\"VATRate\":\"20.00\",\"RTSLabourRate\":\"55.50\",\"DateOut\":\"2017-09-06\",\"TimeOut\":\"16:00\",\"CustomerName\":\"S 4133166Portor\",\"CustomerDetails\":{\"Name\":\"S 4133166Portor\",\"Address\":\"1 Alvin Street\\\\nHungerford\\\\n\\\\n\\\\n\"},\"ListSection\":[{\"Type\":\"2\",\"SectionDesc\":\"Vehicle Details\",\"Cols\":\"2\",\"DisplayRows\":\"10\",\"Rows\":\"10\",\"ListRow\":[{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"Registration:\",\"Col2\":\"PRA4133166\"}},{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"VIN:\",\"Col2\":\"4133166\"}},{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"Make:\",\"Col2\":\"Citroen\"}},{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"Model:\",\"Col2\":\"C3 Picasso\"}},{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"Colour:\"}},{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"Fuel Type:\",\"Col2\":\"Petrol\"}},{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"Last known odometer:\",\"Col2\":\"0\"}},{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"Next service due:\",\"Col2\":\"1200 or 06/07/2018\"}},{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"Registration date:\",\"Col2\":\"06/09/2017\"}},{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"MOT due date:\",\"Col2\":\"06/07/2018\"}}],\"Variant\":{\"Class\":\"CAR\",\"Desc\":\"Car\",\"Variants\":{\"Variant\":[{\"Class\":\"BIKE\",\"Desc\":\"Motorcycle\"},{\"Class\":\"CAR\",\"Desc\":\"Car\"},{\"Class\":\"COMP\",\"Desc\":\"Component vehicle\"},{\"Class\":\"HGV\",\"Desc\":\"Heavy Goods Vehicle\"},{\"Class\":\"LCV\",\"Desc\":\"Light Commercial Vehicle\"}]}}},{\"Type\":\"2\",\"SectionDesc\":\"Today's Service Details\",\"Cols\":\"2\",\"DisplayRows\":\"6\",\"Rows\":\"6\",\"ListRow\":[{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"Repair number:\",\"Col2\":\"20285\"}},{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"Account:\",\"Col2\":\"C0002 - Service Retail Cash Sales\"}},{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"Notes:\",\"Col2\":\"Carry out repair :-\"}},{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"Goods value:\",\"Col2\":\"£52.73\"}},{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"VAT value:\",\"Col2\":\"£10.55\"}},{\"WipNo\":\"20285\",\"Row\":{\"Col1\":\"Total value:\",\"Col2\":\"£63.28\"}}]},{\"Type\":\"4\",\"SectionDesc\":\"Recent Service History\"}],\"VHCCompleted\":\"0\",\"Reminders\":\"0\",\"PreviousVHC\":{\"Available\":\"0\"}},\"Version\":\"3.228\",\"Partition\":\"972\",\"Startup\":\"2017-09-06T15:40\",\"RequestsServiced\":\"1\"}}";
string key = "GetAdvisorWipDetailPageResponse.AdvisorDetailPage.ListSection[0].ListRow[*].[?(#.Col1=='VIN:')].Col2";
var content = JObject.Parse(responseContent);
var value = content.SelectToken(key).ToString();
}
The un-escaped string is here
{"GetAdvisorWipDetailPageResponse":{"AdvisorDetailPage":{"TaxLabel":"VAT","Currency":{"Code":"UKL","Description":"Sterling","Symbol":"£","Precision":"2"},"LastMileage":"0","NetAmount":"52.73","VATAmount":"10.55","TotalAmount":"63.28","VATRate":"20.00","RTSLabourRate":"55.50","DateOut":"2017-09-06","TimeOut":"16:00","CustomerName":"S 4133166Portor","CustomerDetails":{"Name":"S 4133166Portor","Address":"1 Alvin Street\\nHungerford\\n\\n\\n"},"ListSection":[{"Type":"2","SectionDesc":"Vehicle Details","Cols":"2","DisplayRows":"10","Rows":"10","ListRow":[{"WipNo":"20285","Row":{"Col1":"Registration:","Col2":"PRA4133166"}},{"WipNo":"20285","Row":{"Col1":"VIN:","Col2":"4133166"}},{"WipNo":"20285","Row":{"Col1":"Make:","Col2":"Citroen"}},{"WipNo":"20285","Row":{"Col1":"Model:","Col2":"C3 Picasso"}},{"WipNo":"20285","Row":{"Col1":"Colour:"}},{"WipNo":"20285","Row":{"Col1":"Fuel Type:","Col2":"Petrol"}},{"WipNo":"20285","Row":{"Col1":"Last known odometer:","Col2":"0"}},{"WipNo":"20285","Row":{"Col1":"Next service due:","Col2":"1200 or 06/07/2018"}},{"WipNo":"20285","Row":{"Col1":"Registration date:","Col2":"06/09/2017"}},{"WipNo":"20285","Row":{"Col1":"MOT due date:","Col2":"06/07/2018"}}],"Variant":{"Class":"CAR","Desc":"Car","Variants":{"Variant":[{"Class":"BIKE","Desc":"Motorcycle"},{"Class":"CAR","Desc":"Car"},{"Class":"COMP","Desc":"Component vehicle"},{"Class":"HGV","Desc":"Heavy Goods Vehicle"},{"Class":"LCV","Desc":"Light Commercial Vehicle"}]}}},{"Type":"2","SectionDesc":"Today's Service Details","Cols":"2","DisplayRows":"6","Rows":"6","ListRow":[{"WipNo":"20285","Row":{"Col1":"Repair number:","Col2":"20285"}},{"WipNo":"20285","Row":{"Col1":"Account:","Col2":"C0002 - Service Retail Cash Sales"}},{"WipNo":"20285","Row":{"Col1":"Notes:","Col2":"Carry out repair :-"}},{"WipNo":"20285","Row":{"Col1":"Goods value:","Col2":"£52.73"}},{"WipNo":"20285","Row":{"Col1":"VAT value:","Col2":"£10.55"}},{"WipNo":"20285","Row":{"Col1":"Total value:","Col2":"£63.28"}}]},{"Type":"4","SectionDesc":"Recent Service History"}],"VHCCompleted":"0","Reminders":"0","PreviousVHC":{"Available":"0"}},"Version":"3.228","Partition":"972","Startup":"2017-09-06T15:40","RequestsServiced":"1"}}
The following JSONPath query string works with Json.NET:
string key = "GetAdvisorWipDetailPageResponse.AdvisorDetailPage.ListSection[0].ListRow[?(#.Row.Col1=='VIN:')].Row.Col2";
And returns, as a result, 4133166.
Working .Net fiddle.
Why does this work? If I simplify the ListRow section of your JSON, it looks like this:
{
"ListRow":[
{
"WipNo":"20285",
"Row":{
"Col1":"Registration:",
"Col2":"PRA4133166"
}
},
{
"WipNo":"20285",
"Row":{
"Col1":"VIN:",
"Col2":"4133166"
}
}
]
}
You are looking for an entry in the ListRow array for which Row.Col1 has a certain value, specifically "VIN:". When found, you want to select the value of Row.Col2. The JSONPath query string
ListRow[?(#.Row.Col1=='VIN:')].Row.Col2
Does this.
(In your query, you are trying to apply a filter to objects nested directly inside objects (specifically, filtering the value of Col inside the Row object inside the ListRow array item object). This is apparently not implemented in Json.NET as of 10.0.3; see Json.NET JSONPath query not returning expected results for further discussion.)

Validate object using json schema in C#

I'm trying to use a json shema to validate an incoming objet parameter in a C# api.
Here is my api proto :
void BuildSqlQueryFromSegment(JoinDefinition jsonDef);
JsonDefinition is a complex objects, with lots of properties. Properties values are depending each other, this is why simple validateur attribute like Required, Min, Max can't be used here.
One solution would be to pass a string instead of the type object :
void BuildSqlQueryFromSegment(string jsonDef) {
// Check shema here, using Json.NET Schema
// if ok, deserialize and get the JsonDefition object instance
}
I'm able to implment this quite easily, but i find this solution not smart.
What i'm looking for is something like this :
void BuildSqlQueryFromSegment([SchemaValidation('MySchemaResourceHere')] JoinDefinition jsonDef);
Does this kind of attribute exist ? If not, how can i implement it ? How can i act on incoming response json string to check the schema ?
Thanks for your help.
First you need to create JsonSchema from JoinDefinition class
JsonSchemaGenerator schemaGenerator = new JsonSchemaGenerator{ };
var o = new JoinDefinition ();
JsonSchema Objschema = schemaGenerator.Generate(o.GetType());
than get Jsonschema from string
JObject jObject= JObject.Parse(JsonString);
bool valid = jObject.IsValid(Objschema);
If JsonString schema match from class json schema it would return true else false
You will have to use below namespace
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Schema;

Turn a JSON string into a dynamic object

I'm trying to create a dynamic object from a JSON string in C#. But i can't get it done.
Normally i would get a JSON string through a web service call but in this case I simply created a simple class which I turn into a JSON string. Then I try to turn it back into a dynamic object with the exact same structure as if it was an instance of the Entity class. But that's where I'm having trouble.
This is the class that i turn into a JSON string:
public class Entity
{
public String Name = "Wut";
public String[] Scores = {"aaa", "bbb", "ccc"};
}
Then in somewhere in my code i do this:
var ent = new Entity();
// The Serialize returns this:
// "{\"Name\":\"Wut\",\"Scores\":[\"aaa\",\"bbb\",\"ccc\"]}"
var json = new JavaScriptSerializer().Serialize(ent);
dynamic dynamicObject1 = new JavaScriptSerializer().DeserializeObject(json);
dynamic dynamicObject2 = Json.Decode(json);
When I debug this code then i see that the first dynamicObject1 returns a Dictionary. Not really what I'm after.
The second dynamicObject2 looks more like the Entity object. It has a property called Name with a value. It also has a dynamic array called Scores, but for some reason that list turns out to be empty...
Screenshot of empty Scores property in dynamic object:
So I'm not having any luck so far trying to cast a JSON string to a dynamic object. Anyone any idea how I could get this done?
Using Json.Net
dynamic dynamicObject1 = JsonConvert.DeserializeObject(json);
Console.WriteLine(dynamicObject1.Name);
Using Json.Net but deserializing into a ExpandableOBject, a type that is native to C#:
dynamic obj= JsonConvert.DeserializeObject<ExpandoObject>(yourjson);
If the target type is not specified then it will be convert to JObject type instead. Json object has json types attached to every node that can cause problems when converting the object into other dynamic type like mongo bson.

getJSON array parameter data in ashx

I'm having problems passing an array parameter to my .ashx handler. Querystring parameters work fine, but I don't know how to access the array from within the ashx file.
My array is a list of strings called list. This is how I call the getJSON method:
jQuery.getJSON('Handlers/AccessoryUpdateHandler.ashx?action=updateselection&a=' + a_sku, function (list) {
//...
});
In AccessoryUpdateHandler.ashx I can get the querystring parameter using context.Request.QueryString["a"];
But how do I get the array data? I've looked at the Request.QueryString object but there's nothing there that looks like my array.
If there's a better way of doing this I'd also be interested to know.
Perhaps you shouldn't rely on QueryString to pass the kind of data. I would advice you to use "POST" method instead. In either case, using the approach you are using, you wont be able to pass the data as an array. Convert the array into a string. A utility like stringify() can come in handy here if you dont want to convert the array into a string manually. When you extract the string from the Request.QueryString or Request.Form (in case you use post) in the handler, you can easily use JavaScriptSerializer to deserialize using a simple C# class that has a schema similar to your json array.
Edited: Illustration
An array like:
var ary = [{ "id": "1", "name": "Mark" }, { "id": "2", "name": "John"}];
can be converted to a string like:
var aryS = "[{\"id\":\"1\",\"name\":\"Mark\"},{\"id\":\"1\",\"name\":\"Mark\"}]";
then, your QueryString can be constructed as:
jQuery.getJSON('Handlers/AccessoryUpdateHandler.ashx?action=updateselection&a=' + aryS, function (list) {
//...
});
when you go to your handler code, you can have a simple class to help you deserialize the data:
class Rec
{
public int Id { get; set; }
public string Name { get; set; }
}
with that in place, the last step to deserialize is:
string s = context.Request.QueryString["a"]; // HttpContext context
var jserializer = new JavaScriptSerializer();
var ary = jserializer.Deserialize<Rec[]>(s);
Note you have to use Rec[] in the Deserialize function since you are deserializing into an array. ary will be an array of Rec with all your data from your javascript array. You need to reference System.Web.Extensions to use JavaScriptSerializer
EDIT:
I thought you were talking about an array of objects. If its just an array of *long*s, the following lines should be sufficient:
string s = context.Request.QueryString["a"]; // HttpContext context
var jserializer = new JavaScriptSerializer();
var ary = jserializer.Deserialize<long[]>(s);
and you dont need to create a class. you would only need that if you had a array of json objects
I don't think your need for passing arrays should dictate whether you use get or post as others suggest.
Using get, the most idiomatic ways is using the same parameter multiple times. e.g.
Handlers/AccessoryUpdateHandler.ashx?a=1&a=2&a=55
In fact in most frameworks(.NET being the exception) when you get a parameter from url you get a array return(often with one item)
My suggestion is you parse context.Request.RawUrl

Categories