Sunday 19 May 2019

C#, JSON, dynamic...

I've been doing some json stuff in .Net (Json.Net) lately slightly beyond serializing and deserializing. It's basic stuff anyway, but it'll be useful (to me) to dump it here.

First, the basic distinction between the two main classes that we use when dealing with Json in .Net, JsonConvert and JObject. When we already have a .Net class that matches with the json string, we use JsonConvert.DeserializeObject<MyType>. If that's not the case, we use JObject.Parse to deserialize into a JObject, that with the power of dynamic provides us a very convenient way to read and manipulate that json structure. The only source of confusion is that JsonConvert.DeserializeObject<Object>(item), JsonConvert.DeserializeObject<dynamic>(item) and JsonConvert.DeserializeObject(item) will return a JObject, so it's equivalent to doing a JObject.Parse().

The casing difference between camelCase json and PascalCase properties in C# objects continues to make things a bit messy. Out of the box, when deserializing with JsonConvert.DeserializeObject Json.Net will nicely try to find in our .Net class a public property that matches either camel (Person.name) or Pascal (Person.Name) with the json camelCase property ("{'name':'Iyan'}"). So we don't need the horror of changing the standar naming convention.

class PersonCamel
{
 public string name {get;set;}
 public int age {get;set;}
 public override string ToString()
 {
  return this.name + " - " + this.age;
 }
}

class Person
{
 public string Name {get;set;}
 public int Age {get;set;}
 public override string ToString()
 {
  return this.Name + " - " + this.Age;
 }
}

var jsonPerson = @"{
 'name':'Eric',
 'age': 49
}";

personCamel = JsonConvert.DeserializeObject<PersonCamel>(jsonPerson);
Console.WriteLine(personCamel.ToString());
//xuan - 45

person = JsonConvert.DeserializeObject<Person>(jsonPerson);
Console.WriteLine(person.ToString());
//xuan - 45

When serializing our C# class to json, by default JsonConvert will use the name property as it is in our class. So if we want to avoid the pain of ending up with json strings in PascalCase, we'll have to help the serializer a bit with one of these 2 techniques:

JsonConvert.SerializeObject(person, new JsonSerializerSettings
{
 ContractResolver = new DefaultContractResolver
 {
  NamingStrategy = new CamelCaseNamingStrategy()
 }
});


JsonConvert.SerializeObject(person, new JsonSerializerSettings 
{ 
 ContractResolver = new CamelCasePropertyNamesContractResolver() 
});

Let's move now to more interesting stuff with JObject, dynamic and explicit/implicit conversions (I had already talked about this in this post). Let's see some code:

var jsonResult = @"{
'result':'OK',
'users': [
 {
 'name':'xuan',
 'age': 45
 }, 
 {
 'name':'Francois',
 'age': 47
 }
  ]
}";

var jsonPerson = @"{
 'name':'Eric',
 'age': 49
}";

void TestJObjQuerying(string jsonResult, string jsonPerson)
{
 Console.WriteLine("TestJObjQuerying");
 
 dynamic resultJObj = JsonConvert.DeserializeObject(jsonResult);
 //to be able to use a linq query on the array we need to cast as JArray
 //then, as JArray implements IEnumerable of JToken we need to cast to dynamic again
 Console.WriteLine(String.Join(",", ((JArray)(resultJObj.users)).Select(it => (((dynamic)it).name))));
 
 
 
 dynamic personJObj = JObject.Parse(jsonPerson);
 
 var name1 = personJObj.name;
 Console.WriteLine(name1.GetType().Name);//JValue
 
 string name2 = personJObj.name; //implicit conversion
 Console.WriteLine(name2.GetType().Name); //string
 
 var name3 = (string)(personJObj.name); //explicit conversion
 Console.WriteLine(name3.GetType().Name);//string
 
 Func myFunc = jObj => jObj.name; //implicit conversion for the return typle
 var name4 = myFunc(personJObj);
 Console.WriteLine(name4.GetType().Name);//string
 
}

TestJObjQuerying(jsonResult, jsonPerson);

We know that resultJObj.users is a JArray, but we need to hint the compiler with a cast, otherwise the compiler will consider it just as dynamic (the field of a dynamic object is a dynamic). JArray provides the Select method not as an own method, but as an Extension method available because it implements IEnumerable. Extension methods and dynamic do not play well together, so we need that casting for the compiler to set the call to Linq.Enumerable.Select. That Select returns an IEnumerable so in order for the access to the name property to work we need to do a casting to dynamic.

The next lines in the method are a reminder of how implicit and explicit conversions make JObjects and dynamic work like magic.

A long while ago I already posted about a very interesting possibility, deserialize an object into a JObject and then take one part of that structure for which we have a .Net Type and convert it to that type by means of ToObject. In my old sample I was passing a serializer to ToObject because of the camel to Pascal, but now, same as I've just explained for JsonConvert, this is managed automatically.

 dynamic obj = JObject.Parse(jsonResult);
 personJObject = obj.users[0];
 var person = personJObject.ToObject<Person>(); //camel to Pascal works good automatically 

The inverse procedure can also be useful. We have a .Net class and we want to serialize it to json with some additional fields. We'll use FromObject like this:

var person = new Person{
 Name = "Jean",
 Age = 52
};
dynamic jPerson = JObject.FromObject(person);
jPerson.City = "Marseille";
Console.WriteLine(jPerson.ToString());
 
//{
//  "Name": "Jean",
//  "Age": 52,
//  "City": "Marseille"
//}

The problem with the above is that there's not an out of the box way to serialize the JObject to camelCase json. You'll have to use some more elaborate technique as the one described here.

No comments:

Post a Comment