I'm using the Json.Net library (for .NET v3.5) to handle deserialisation of responses from the EBoss CRM API. As far as I've seen the API documentation is rather spotty, and so I'm having to poke at it myself to see what kind of responses can be elicited.
I wanted to have a single wrapper class EBossApiResponse<T> that would provide properties for any error messages that the API sent back, plus a property containing the deserialised data of type T (which would be restricted to an abstract base class which acts as the underlying type of any classes I created to model the returned data).
My initial poking revealed the first problem. A valid request can return an array of objects, such as: https://ebosscrm.com/api.php/job_type.json
This will easily deserialise to a hypothetical T of List<EBossJobType>. Note that there's no error property in the results.
A malformed request to that same endpoint however returns something different: https://ebosscrm.com/api.php/job_type.json?search[foo]=1
In this case, an array is returned which contains a single object with a single property named message.
(Note, there are calls for which having a parameter named search[something] is valid, but foo is never a valid something)
There is also the possibility for an explicit error to be returned. It looks to me like the API is catching exceptions and formatting a JSON response containing the debug information: https://ebosscrm.com/api.php/candidates.json?uid=114&api_key=f34js3kj
In this case, the returned JSON is not an array, it's a single object. I'm not sure how to cater for these differing response structures. My initial thinking was to have something like:
protected bool IsNonDataResponse(string response)
{
JObject o = JObject.Parse(response);
return o.SelectToken("message") != null || o.SelectToken("error") != null;
}
I could use this method to then either deserialise directly to the right EBossApiResponse<T> type (if true, populating error messages but leaving the .Data property == null), or deserialise to the right List<EBossEntityType>, create a new EBossApiResponse<List<EBossEntityType>>, and set its .Data property.
Then I realised that o.SelectToken("message") won't work, because o would be an array, not an object. Can .SelectToken() take a pattern of the form "[0].message" to get the message property of the first item?
How about this?
JObject o = JObject.Parse(response);
JArray arr = o as JArray;
return (arr != null && (JObject)arr[0].SelectToken("message") != null)
|| o.SelectToken("error") != null;
Or am I barking completely up the wrong tree, and there's a more elegant solution to this whole thing?
You could at least replace JObject.Parse() with JToken.Parse() because JToken is the base class for both JObject and JArray. Then you can check whether the result is either a JArray or a JObject (or maybe a JValue like an integer).
var result = JToken.Parse(response);
if (result is JArray)
{
// ...
}
else if (result is JObject)
{
// ...
}
Have also a look at the documentation for JToken.
Related
Most of the time we don't, by default, have defined classes in our static language of choice, which presents white a quandary over how to deserialize the e.g. JSON returned from a call to a public and open wen api, dedicated to no single language, except the "language" of e.g. HTML and JSON.
In .NET, these days, nearly all api queries are done with HttpClient, something like the Google Books API ISBN query below:
public class GoogleBooksClient
{
private const string IsbnUrl = "books/v1/volumes?q=isbn:{0}";
private static HttpClient _client = new HttpClient();
...
public async Task<object> GetDetailsByIsbn(string isbn)
{
var json = await _client.GetStringAsync(string.Format(IsbnUrl, isbn));
dynamic objX = JsonConvert.DeserializeObject(json);
return objX;
}
}
The biggest problem here is that whether objX is declared var', object, or dynamic, it is always a reference to a big, ugly JObject instance when DeserializeObject is called without a known type. In this case, the JSON object returned is incredibly complex, and there be dragons waiting for those who endeavour to write a C# class that the said JSON can be parsed into.
This is an ideal, and intended, opportunity for the use of a C# dynamic object, where as the JSON is parsed, properties (and rarely functions for API responses) can be recursively added to the said dynamic object.
Instead, NewtonSoft.JSON tightly binds the data held by the JSON with the heavy red-tape ligature of a quite opaque Jobject data structure. I'm surprised and disappointed that having come so far, NewtonSoft cannot simply extract a pure dynamic object, without all the obfuscating bureaucracy of their JObject maze.
Is there no other way to simply parse the JSON into a C# dynamic object, just like it would be parsed into an explicitly dynamic JavaScript object in other scenarios?
ADDED: By "obfuscating bureaucracy of their JObject maze", I mean the following code:
var json = JsonConvert.SerializeObject(new Person {Id = 196912135012878, Age = 47, Name = "Brady"});
var jObj = JsonConvert.DeserializeObject(json);
produces a JObject instance that my debugger shows as:
This looks, to me, much more like something that should be used internally during parsing of the JSON, and not an end product of that parsing, which should be a pure representation of only the properties parsed from that JSON. That would ideally be a C# dynamic object with no properties for use by any parser.
Rather than a JObject, you can deserialize to an ExpandoObject, which is supported by Json.NET's built-in converter ExpandoObjectConverter. If an array, you can deserialize to a List<ExpandoObject> then select to a List<dynamic>. If a primitive value, you could return JValue.Value.
For instance, if you know in advance your JSON represents an object, just do:
dynamic dyn = JsonConvert.DeserializeObject<ExpandoObject>(json);
If you don't know in advance what the root JSON container might be, you can load it as a JToken and use the following extension method:
public static class JsonExtensions
{
public static dynamic ToDynamic(this JToken token)
{
if (token == null)
return null;
else if (token is JObject)
return token.ToObject<ExpandoObject>();
else if (token is JArray)
return token.ToObject<List<ExpandoObject>>().Cast<dynamic>().ToList();
else if (token is JValue)
return ((JValue)token).Value;
else
// JConstructor, JRaw
throw new JsonSerializationException(string.Format("Token type not implemented: {0}", token));
}
}
Then do:
dynamic dyn = JsonConvert.DeserializeObject<JToken>(json).ToDynamic();
Sample fiddle.
So I'm able to send simple objects like strings no problem. I'm also able to send the same dictionary to my javascript client. However, I can't seem to get my C# client to pick it up. I'm assuming this is because in the javascript client the type being sent doesn't need to be specified, but in C# it does.
Here's the client setup
connection = new HubConnection(ServerURI);
hubProxy = connection.CreateHubProxy("ControllerHub");
hubProxy.On<Dictionary<Tuple<string, string>, RequestEntry>>("ReceiveTickerStateData", overviewDictionary => receiveTickerStateData(overviewDictionary));
connection.Start().Wait();
Here's the server code that is getting triggered to send the dictionary, but the client never receives it.
public void GetTickerStateData()
{
Clients.Caller.ReceiveTickerStateData(DealTickerState.Instance.GetRequestDict);
}
Any insight or ideas are appreciated. Thanks!
------------------------Additions
Further testing has yielded that it is the fact that I'm using a tuple (string, string) as the key for the dictionary. Apparently the JSON deserializer does not know how to handle this. Gives me the error:
Could not convert string (DEFAULT, IDCO) to dictionary key type
System.Tuple`2[System.String,System.String]. Create a TypeConverter
to convert from the string to the key type object. Path '['(DEFAULT,
IDCO)']', line 1, position 19.
I got this by manually serializing and sending the string.
I tried sending a dictionary with just a string as the key and it works fine both as a serialized string and also just as is. (ie signalR automatically serializing)
So now to decide, do I create the type converter? or do I send the data using a simpler collection structure that works.
Yes, as JSON. Use JSON.NET, the included JavaScriptSerializer won't work. You can get JSON.NET from NuGet. Convert your dictionary to JSON and send it as a string. On the client-side, just deserialize it using the built-in JSON.parse() method.
You can also create an intermediary type that stores the dictionary during transfer (I use List<KeyValuePair<T1,T2>>), it's important that you validate that there aren't any duplicate keys when deserialising this from a client.
[Serializable]
public class SerializableDictionary<T1,T2>
{
public List<KeyValuePair<T1,T2>> SerializedDictionary { get; set; }
public SerializableDictionary( Dictionary<T1,T2> dictionary )
{
if( dictionary != null && dictionary.Count > 0 )
{
SerializedDictionary = dictionary.Select( item => item ).ToList();
}
else
{
throw new ArgumentNullException( "Cannot serialize a null or empty dictionary" );
}
}
public static explicit operator SerializableDictionary<T1,T2>(Dictionary<T1,T2> dictionary)
{
return new SerializableDictionary<T1,T2>(dictionary);
}
public static explicit operator Dictionary<T1,T2>(SerializableDictionary<T1,T2> dictionary)
{
if ( dictionary.SerializedDictionary == null ) return null;
return dictionary.SerializedDictionary.ToDictionary( item => item.Key, item => item.Value );
}
}
This technique wont work for every dictionary (for example, those stored in exceptions) as they can't all be easily suplemented for this service aware dictionary.
Seems that the real answer is Yes, it is possible, but you need to write a custom JSON converter. You can find more info on that here, though the answer does seem a tad incomplete.
How to properly serialize tuple as key dictionary
So I'm trying to control deserialization by reading a json object as a JObject, deleting some fields, and then deserializing it again to my target object using Json.Net. The problem is, any time I try to delete a field, I get the error:
An unhandled exception of type 'Newtonsoft.Json.JsonException'
occurred in Newtonsoft.Json.dll
Additional information: Cannot add or remove items from
Newtonsoft.Json.Linq.JProperty.
Here's my (simplified, but still causing the error) code:
JToken token = (JToken)JsonConvert.DeserializeObject(File.ReadAllText(fileName));
foreach (JToken inner in token["docs"])
{
if (inner["_id"] != null)
inner["_id"].Remove();
MyObject read = new MyObject();
JsonConvert.PopulateObject(inner.ToString(), read);
Values.Add((MyObject)JsonConvert.DeserializeObject(inner.ToString(), typeof(MyObject)));
}
The json is a very large file where the docs array contains many elements as follows (again simplified for clarity):
{
"docs": [
{
"Time": "None",
"Level": 1,
"_id": "10208"
},
{
"Time": "None",
"Level": 1,
"_id": "10209"
}
]
}
Alternatively if there's a better way to deserialize JSON to a specific type, but still ignoring additional fields, that would be a fine alternative.
Assuming Values is a List<MyObject> and your MyObject class looks like this:
class MyObject
{
public string Time { get; set; }
public int Level { get; set; }
}
you can replace all that code with the following to get the result you want:
string json = File.ReadAllText(fileName);
Values = JToken.Parse(json)["docs"].ToObject<List<MyObject>>();
This works because Json.Net will ignore missing properties by default. Since the MyObject class does not contain an _id property to deserialize into, you don't need to jump through hoops trying to remove it from the JSON.
Explanation of why Remove() didn't work
JToken.Remove() removes a JToken from its parent. It is legal to remove a JProperty from its parent JObject, or to remove a child JToken from a JArray. However, you cannot remove the value from a JProperty. A JProperty must always have exactly one value.
When you ask for token["_id"] you get back the value of the JProperty called _id, not the JProperty itself. Therefore you will get an error if you try to call Remove() on that value. To make it work the way you are doing, you'd need to use Parent like this:
if (inner["_id"] != null)
inner["_id"].Parent.Remove();
This says "Find the property whose name is _id and give me the value. If it exists, get that value's parent (the property), and remove it from its parent (the containing JObject)."
A more straightforward way to do it is to use the Property() method to access the property directly. However, this method is only available on JObject, not JToken, so you would either need to change the declaration of inner to a JObject or cast it:
foreach (JObject inner in token["docs"].Children<JObject>())
{
JProperty idProp = inner.Property("_id");
if (idProp != null)
idProp.Remove();
...
}
Lastly, as mentioned in the comments, if you're using C# 6 or later you can shorten the code a bit using the null-conditional operator:
inner.Property("_id")?.Remove();
Based on brillian answer from Brian, you can do simply this in your case:
var inner_id = inner["_id"] as JProperty;
if (inner_id != null)
inner_id.Remove();
I am developing a windows 8 app, and i have some javascript that stores a serialized object into roaming settings, i.e:
var object = [{"id":1}, {"id":2}]
roamingSettings.values["example"] = JSON.stringify(object);
I also i have a c# part to the application (for running a background task), that needs to read that JSON, and turn it into an object so i can iterate over it. And this is where i am having some issues, i am using JSON.NET to do the work, but every thing i turn turns up with an error:
// this looks like "[{\"id\":1},{\"id\":2}]"
string exampleJSON = roaming.Values["example"].ToString();
// dont know if this is correct:
List<string> example = JsonConvert.DeserializeObject<List<string>>(exampleJSON );
That give an error of:
Error reading string. Unexpected token: StartObject. Path '[0]', line 1, position 2.
So i am at a loss of what to do, i have been working on it for last few hours, and i am quite unfamiliar with c#, so resorting to the help of stackoverflow ;D
Thanks in advance for any help :)
Andy
Json.Net has a nice method DeserializeAnonymousType. No need to declare a temporary class.
string json = "[{\"id\":1},{\"id\":2}]";
var anonymous = new []{new{id=0}};
anonymous = JsonConvert.DeserializeAnonymousType(json,anonymous);
foreach (var item in anonymous)
{
Console.WriteLine(item.id);
}
You can even use the dynamic keyword
dynamic dynObj = JsonConvert.DeserializeObject(json);
foreach (var item in dynObj)
{
Console.WriteLine(item.id);
}
You are trying to parse your JSON array into a List of strings, which doesn't work. The JSON object you provide is actually a list of objects containing an integer property called 'id'.
Perhaps try creating a class (say, MyClass) with just that property, and deserialize into List.
Your json containts a collection of objects with an id property, something like this:
class IdObject {
public int id { get; set; }
}
You could then do:
JsonConvert.DeserializeObject<List<IdObject>>(exampleJSON);
Because the IdObject class has a property id to match your json serialized value, it will be mapped back.
I'm receiving a JSON string in a MVC4/.NET4 WebApi controller action. The action's parameter is dynamic because I don't know anything on the receiving end about the JSON object I'm receiving.
public dynamic Post(dynamic myobject)
The JSON is automatically parsed and the resulting dynamic object is a Newtonsoft.Json.Linq.JContainer. I can, as expected, evaluate properties at runtime, so if the JSON contained something like myobject.myproperty then I can now take the dynamic object received and call myobject.myproperty within the C# code. So far so good.
Now I want to iterate over all properties that were supplied as part of the JSON, including nested properties. However, if I do myobject.GetType().GetProperties() it only returns properties of Newtonsoft.Json.Linq.JContainer instead of the properties I'm looking for (that were part of the JSON).
Any idea how to do this?
I think this can be a starting point
dynamic dynObj = JsonConvert.DeserializeObject("{a:1,b:2}");
//JContainer is the base class
var jObj = (JObject)dynObj;
foreach (JToken token in jObj.Children())
{
if (token is JProperty)
{
var prop = token as JProperty;
Console.WriteLine("{0}={1}", prop.Name, prop.Value);
}
}
EDIT
this also may help you
var dict = JsonConvert.DeserializeObject<Dictionary<string, object>>(jObj.ToString());