Pimp my json contract

In last post i presented how to obtain C# object from json format using Newtonsoft.Json library in UWP. In this post let's focus on how to modify output class for our needs. Before we start, i present below, the trimmed version of json returned by TMDb Api, current version of RootObject that is our output class, and five lines of code that fetch correct json from TMDb and converts it to the instance of RootObject.

{
"adult": false,
"backdrop_path": "/hNFMawyNDWZKKHU4GYCBz1krsRM.jpg",
"belongs_to_collection": null,
"genres": [
{
"id": 18,
"name": "Drama"
}
],
"poster_path": "/2lECpi35Hnbpa4y46JX0aY3AWTy.jpg",
"production_companies": [
{
"name": "20th Century Fox",
"id": 25
},
{
"name": "Fox 2000 Pictures",
"id": 711
},
{
"name": "Regency Enterprises",
"id": 508
}
],
...
}

public class RootObject
{
public bool adult { get; set; }
public string backdrop_path { get; set; }
public object belongs_to_collection { get; set; }
public List<Genre> genres { get; set; }
public string poster_path { get; set; }
public List<ProductionCompany> production_companies { get ; set ; }
...
}
http = new HttpClient();
http.BaseAddress = new Uri( "http://api.themoviedb.org/3/");
var response = await http.GetAsync("movie/550?api_key=da63548086e399ffc910fbc08526df05" );
var jsonData = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<RootObject>(jsonData);
Custom Property Names
First things first. Let's do something with this sneaky snake_case, none of the proud C# coders want to have such naming convention mismatch in his project. So that's the object definition that we want to see:
public class RootObject
{
public bool Adult { get; set; }
public string BackdropPath { get; set; }
public object BelongsToCollection { get; set; }
public List<Genre> Genres { get; set; }
public string PosterPath { get; set; }
public List<ProductionCompany> ProductionCompanies { get ; set ; }
...
}
Where's the catch? As i mentioned in previous post, Newtonsoft.Json conversion is going to work perfectly when name of properties in RootObject match name in json file. Default matching is also case insensitive so, Adult and Genres are going to be still correctly populated. All other properties that contained originally an underscore in name are not going to be matched and going to be populated with default values. To handle all those cases we have to use overload of JsonConvert.DeserializeObject that accepts JsonSerializerSetting instance as a parameter.
JsonSerializerSettings has one useful property (not only, obviously, but for our example;)) - ContractResolver - and it's used to resolve a JsonContract for given type. As you can easily guess, JsonContract simply describes how to map properties between json and data object.
To make usage of our ContractResolver we have to create class that derives from DefaultContractResolver and override single method called ResolvePropertyName - this method simply takes property name of our data object (PascalCase) and returns name of property in json (snake_case). Below you can find the definition of custom resolver and usage of it. That will make our RootObject instance again full of data ;)
public class UnderscoreToPascalCaseContractResolver : DefaultContractResolver
{
protected override string ResolvePropertyName(string propertyName)
{
var builder = new StringBuilder();
foreach (var c in propertyName)
{
if (char.IsUpper(c))
builder.Append( '_');
builder.Append(char.ToLower(c));
}
if ( char.IsUpper(propertyName, 0))
builder.Remove(0, 1);
return base.ResolvePropertyName(builder.ToString());
}
}

http = new HttpClient();
http.BaseAddress = new Uri( "http://api.themoviedb.org/3/");
var response = await http.GetAsync("movie/550?api_key=da63548086e399ffc910fbc08526df05" );
var jsonData = await response.Content.ReadAsStringAsync();
var settings = new JsonSerializerSettings () { ContractResolver = new UnderscoreToPascalCaseContractResolver () };
var result = JsonConvert.DeserializeObject<RootObject>(jsonData, settings);
JsonConverter attribute
Next thing. Two properties poster_path and backdrop_path are returned by API as a relative url paths to images, but i want to keep them in RootObject instance as an absolute paths. How to achieve this? it's very simple, we can make usage of JsonConverterAttribute and populate it with custom JsonConverter class that will combine absolute path on the fly during conversion.
Below you can find example of ImageAbsolutePathJsonConverter. JsonConverter class provides three abstract method:
  • WriteJson - used when object is serialized to json. Not used in our case so i've also override virtual CanWrite property
  • ReadJson - used when object is deserialized from json. There is also similarly related virtual property named CanRead
  • CanConvert - used only when you provide JsonConverter via JsonSerializerSetting to indicate when converter should be applied in serialization. Not used in case of JsonConverterAttribute, so we can ignore it.

public class ImageAbsolutePathJsonConverter : JsonConverter
{
protected virtual string BaseImageUrl { get; } = Urls.Images .WidthOriginal;

public override bool CanWrite { get; } = false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotSupportedException("This converter supports only read mode.");
}
public override bool CanConvert(Type objectType)
{
return false;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if ( string.IsNullOrEmpty(BaseImageUrl))
throw new ArgumentException("baseImageUrl cannot be null or empty.");
return string.Format(BaseImageUrl, reader.Value);
}
}

public class RootObject
{
public bool Adult { get; set; }
[JsonConverter(typeof(ImageAbsolutePathJsonConverter))]
public string BackdropPath {get;set; }
public object BelongsToCollection { get; set; }
public List<Genre> Genres { get; set; }
[JsonConverter(typeof(ImageAbsolutePathJsonConverter))]
publicstring PosterPath {get;set; }
public List<ProductionCompany> ProductionCompanies { get ; set ; }
...
}

JsonProperty attribute
Last thing for today. Let say that i want to have ProductionCompanies as a simple List<string> instead list of complex object. How to achieve this? We have to again specify custom JsonConverter, however we are not going to use JsonConverterAttribute again, because we want to apply conversion for each element in array. Appropriate attribute in this case is JsonPropertyAttribute that provides us with ItemConverterType - optional constructor parameter that works in same way as constructor of previous attribute. It accepts type of custom JsonConverter. Tip: In case of single object (instead of array) we could take advantage of optional parameter like: PropertyName = "name" instead of creating separate converter. Below example shows how to select property path from JsonReader object and how to apply custom converter to JsonProperty attribute.

public class NameJsonConverter : JsonConverter
{
protected virtual string PropertyPath { get; } = "name";

public override bool CanWrite { get; } = false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotSupportedException("This converter supports only read mode.");
}
public override bool CanConvert(Type objectType)
{
return false;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jObject = JObject .Load(reader);
var value = jObject.SelectToken(PropertyPath).Value<string >();
return value;
}
}

public class RootObject
{
public bool Adult { get; set; }
[JsonConverter(typeof(ImageAbsolutePathJsonConverter))]
public string BackdropPath {get;set; }
public object BelongsToCollection { get; set; }
public List<Genre> Genres { get; set; }
[JsonConverter(typeof(ImageAbsolutePathJsonConverter))]
public string PosterPath {get;set; }
[JsonProperty(ItemConverterType = typeof(NameJsonConverter))]
public List<string> ProductionCompanies { get ; set ; }
...
}
Cheers.