I was deserializing an object that requires to be a string at the client but it’s actually a structure inside the database, a person’s name, saved as a Name { FirstName:”, LastName: ”, … } type of object. By using Newtonsoft’s deserializer into a model such model can’t just have a string Name as Newtonsoft’s deserializer won’t know how to parse the object into a simple string, so instead I had to declare the property as the same type of object that the name is saved into the database: PersonName.
Now I wanted to transform it into a string when re-serializing it to send it to the client, which could do with a mapper but decided to try Newtonsoft’s JsonSerializer attribute to see if it works and to my amazement it did the job beautifully:
public class PersonNameJsonConverter : Newtonsoft.Json.JsonConverter { public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { var name = value as PersonName; writer.WriteValue(name != null ? name.GetFullName() : ""); } public override bool CanConvert(Type objectType) => typeof(PersonName).IsAssignableFrom(objectType); public override bool CanRead => false; public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { throw new NotImplementedException("Should never get here."); } }
The point here is to decide how to write the value once we get to that point. In my case this is very easy as I just want to parse the object to String and my class PersonName already has a method to do such parsing: “GetFullName”, so I just check if it’s not null (remember this is not a plain string) and if not I call said method. Though before I do that I need to cast the value as by default it comes as type object.
CanConvert is part of the JsonConverter implementation that allows to determine if I can call WriteJson or not, I could just return a true value but decided to check the type of the value I’m handling just in case someone decides to attach my converter into the wrong type of attribute 😉
CanRead is used in the other direction, in case we want to add this converter on reading time so when we deserialize the object we get that instead, that is not my case for I want to actually tell it how to serialize it, not how to deserialize it. If that were the case though, I would have to use ReadJson() to implement such feature instead and return the value I wanted instead of writing it.
PersonName it’s just the type I created which is not part of this code as it’s not relevant to the explanation :p
Very important: Remember to inherit from Newtonsoft.Json.JsonConverter as this converter is an extension of such to create a decorator!
How do I use it?
Once the converter it’s created you only need to attach it as an Aspect to your property:
public class QmContactSearchResult { [JsonConverter(typeof(PersonNameJsonConverter))] public PersonName Name { get; set; } }
Now when Newtonsoft’s deserializer reads the json it will know where to put the data as the Name in this class matches the type of the serialized Name type, but when I write it to send the response to the client it will write it as a plain string type. I could have done it the other way around and tell it how to read it so it’s already a string, or I could have used a mapper, but it’s always interesting to learn new ways to achieve something 🙂
Tips
I used WriteValue() to tell the serializer to automatically handle the value type, so as this is a string it will automatically add the braces “” to it, if instead you decide to use WriteRawValue() it won’t add such braces by default which could cause a crash when reading the malformed json. So be careful!
The parameter JsonSerializer can be used to set other flags and config values to the serializer, which in my case it’s not necessary as my converter it’s very basic.
A converter can also be used at a class level instead than a property by setting the aspect there, which gives you a bit more of complexity and makes type checking far more important. You could essentially attach this to an entire class and it would automatically parse all PersonName attributes it finds as it has a check at the CanWrite() to prevent from converting other types of value.