JSON
To deserialize from JSON you must enable get and set on class properties otherwise deserialization will not be attempted for that class property.
You can convert a string to an object by using the Deserialize
method on JsonSerializer
typed with the object that you want to convert the JSON into.
string objectJson = "{ \"Id\": \"0\", \"Name\": \"Xan\", \"Age\": \"400\" }";
var user = JsonSerializer.Deserialize<User>(objectJson);
/*
User class structure:
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
*/
You can set different serialization options such as remove case sensitivity, by using the JsonSerializerOptions
object as argument to the Deserialize
method. Note the lowercase name
property in this example.
string objectJson = "{ \"Id\": \"0\", \"name\": \"Xan\", \"Age\": \"400\" }";
var user = JsonSerializer.Deserialize<User>(objectJson, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
You can define custom deserializations in JSON using the JsonPropertyName
attribute. This is useful if you have one or two attributes in your incoming JSON that do not match the dotnet naming schema allowing you to define a custom source for them. The example below deserializes from a snake case version of some_property
into the standard dotnet pascal case SomeProperty
.
public class Data
{
[JsonPropertyName("some_property")]
public string SomeProperty { get; set; }
}
You can deserialize enum properties from JSON by adding a JsonStringEnumConverter
object to the list of Converters
as part of your JsonSerializerOptions
object. It seems that this inherits the case inseitivity of the serializer options.
// load raw json above
var serializerOptions = new JsonSerializerOptions();
serializerOptions.PropertyNameCaseInsensitive = true;
serializerOptions.Add(new JsonStringEnumConverter());
var deserializedObject = JsonSerializer<MyClass>(rawJson, serializerOptions);
Newtonsoft
Don’t use it.
You can convert a string to an object by using the DeserializeObject
method on JsonConvert
typed with the object that you want to convert the JSON into.
string objectJson = "{ \"Id\": \"0\", \"Name\": \"Xan\", \"Age\": \"400\" }";
var user = JsonConvert.DeserializeObject<User>(objectJson);
/*
User class structure:
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
*/
Custom Deserialization
You can write custom deserialization code by extending the JsonConverter<T>
class. This class implements a Read
and Write
method for deserializing and serializing data. The T
will be the object to which you deserialize. Custom deserializers slot directly into the existing serialization framework that dotnet offers. This means that if you have a set of nested objects that you want to deserialize using the ordinary built in serializers that contain an object that requires custom deserialization, your custom deserializer will only run for the specific nested object that needs deserialization.
The example below demonstrates with an Operator
class that contains a list of Order
objects that require custom deserialization. When running the custom deserializer will only trigger once it reaches one of the order objects in the Orders
list.
public class Operator
{
string Name { get; set; }
int id { get; set; }
List<Order> Orders { get; set; }
}
public class CustomOrderDeserializer : JsonConverter<Order>
{
// custom deserialization code
}
The associated JSON would be as follows…
{
"name": "Sonitaf",
"id": 4321766555,
"orders":
[
{
"id": "76$$09_k"
},
{
"id": "6^jJ0-f"
}
]
}
You can use your custom deserializer when deserializing by adding it to the list of Converters
on your JsonSerializerOptions
that are an argument for the actual Deserialize
function.
var serializerOptions = new JsonSerializerOptions();
// Add an instance of your deserializer code here
serializerOptions.Converters.Add(new MyCustomDeserializer());
var jawJson = File.ReadAllText("./data.json");
var result = JsonSerializer.Deserialize<MyClass>(rawJson, serializerOptions);
This is important to remember when traversing the JSON reader for your object. Your custom reader will traverse from the beginning of the object and must only return the created object once it has reached its end. Functionally this means you must reach a JsonTokenType.EndObject
enum variant when returning. If you do this incorrectly you will get a read too much or not enough
exception when deserializing. More detail here.
if (reader.TokenType == JsonTokenType.EndObject)
{
// return the deserialized object
}
You can read the tokenized JSON of an object using the Utf8JsonReader
object that is passed into the Read
method of the JsonConverter<T>
class. The signature of this method is shown below where T
is the object that will be returned from the deserialization.
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options);
You can read through the tokens in the reader using a while
loop with the Read
method on the reader. On each loop iteration the next JSON token will be accessible on the reader.
public override T Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
while(reader.Read())
{
// moves to the next token in the reader
}
}
You can check the type of the current json token using the TokenType
property.
public override T Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
while(reader.Read())
{
if (reader.TokenType == JsonTokenType.PropertyName)
{
// do something if we reached a property
}
}
}
You can get the value of a token if it contains a value using the GetString
method. For example, if we want to get the value of a property or the name of a field.
public override T Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
while(reader.Read())
{
if (reader.TokenType == JsonTokenType.PropertyName)
{
if (reader.GetString() == "id")
{
// do something if that property was called id
}
}
}
}
You can manually read to the next token within a while
loop for reading. This does not cause any problems and will just cause the next loop to start at the nesting that you reached in your traversal within the loop. The example below will read 2 tokens per loop.
public override T Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
while(reader.Read())
{
reader.read()
}
}
A more useful application of this can be seen below where the reader reaches a property called id
and then reads to the next token to get the value of that property. When the while
loop resumes it will move to the next token after the value that was read.
public override T Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
while(reader.Read())
{
if (reader.TokenType == JsonTokenType.PropertyName)
{
if (reader.GetString() == "id")
{
reader.Read();
var val = reader.GetString();
// do something with the property value
}
}
}
}
At first it appears like you can only traverse the Utf8JsonReader
once through. However, you can traverse the reader multiple times and even duplicate the reader content by passing the reader into a function without the ref
keyword. Not using the ref
keyword will not mutate the “base” reader that the Read
method uses. If you pass the reader
into a function with the ref
keyword that function will continue to mutate the state of the reader that the deserializer passes out for the next stage of deserialization.
public override T Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
ReadDataThrough(reader);
// reader is still positioned at start
Assert.Equals(reader.TokenType, JsonTokenType.StartObject);
ReadDataThroughMut(ref reader);
// reader is now positioned where the mut function finished
Assert.Equals(reader.TokenType, JsonTokenType.StartArray);
// ---SNIP--- other deserialization stuff
}
// read through the entirety of the json WITHOUT mutating the base reader
public void ReadDataThrough(Utf8JsonReader reader)
{
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject) { break; }
}
}
// read through to a start array property that mutates the base reader
public void ReadDataThroughMut(ref Utf8JsonReader reader)
{
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.StartArray) { break; }
}
}
Polymorphic Deserialization
There’s some general info on polymorphic deserialization in dotnet here.
The example below shows how to accomplish polymorphic deserialization in dotnet. The Person
class contains a Job
field of type Role
that can be a concrete polymoprhic instance of the abstract Role
class, either Doctor
or Dentist
. In this case the custom deserializer needs to traverse the JSON of the object until it finds a discrimator - i.e. some clue in the object structure about what type of subclass this object is - in this case we use the name of the property on the object; if its a Doctor
object it will have the property HospitalId
otherwise its a dentist. The reader then creates an instance of the correct subclass and returns that instance when it reaches the end of the reader.
The example below also demonstrates that if you have to process JSON with multiple properties that are unordered and occur before your discriminator you may have to store these values while you traverse the JSON tree until you find your discriminator then recreate the correct object type and copy accross all the stored values. Here this is encapsulated with the Salary
property which is stored intermediately on the role
value until the discriminator is found, then if the value is there it is copied accross to the new discriminated object. It needs to be done this way as depending on the ordering of the properties in the JSON file the Salary
value could be found before or after the discriminator.
public class Person
{
string Name { get; set; }
Role Job { get; set; }
}
public abstract class Role
{
public int Salary { get; set; }
public abstract string JobAction();
}
public class Doctor : Role
{
public int HospitalId { get; set; }
public override string JobAction()
{
return "Fixing this dude's spleen.";
}
}
public class Dentist : Role
{
public string EnamelSpeciality { get; set; }
public override string JobAction()
{
return "Fixing this lady's molar.";
}
}
public class RoleDeserializer : JsonConverter<Role>
{
public override Role Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
// default to an empty doctor role to hold intermediate values before discrimination
Role role = new Doctor();
while(reader.Read())
{
if (reader.TokenType == JsonTokenType.PropertyName)
{
if (reader.GetString() == "HospitalId")
{
reader.Read();
var id = Int32.Parse(reader.GetString());
role = new Doctor()
{
HospitalId = id
Salary = role.Salary != null? role.Salary : null
}
}
else
{
reader.Read();
var speciality = reader.GetString();
role = new Dentist()
{
EnamelSpeciality = speciality
Salary = role.Salary != null? role.Salary : null
}
}
// update the salary on the object - shared by both role instances
if (reader.GetString() == "Salary")
{
reader.Read();
role.Salary = Int32.Parse(reader.GetString());
}
}
// return object once end is reached
if (reader.TokenType = JsonTokenType.EndObject)
{
return role;
}
}
// return the empty object if we reach the end with out anything
// could raise an exception here if required
return role;
}
// there will also be an unimplmeneted version of the Write function below
}
The above solution works, but is awkward because it requires you to deserialize everything manually while also figuring out what the object type is. Given that you can actually traverse the Utf8JsonReader
’s content multiple times you can instead use a discriminator function with a copy of the reader to find the correct type then use standard deserialization to with a built in deserializer to deserialize the correct sub class.
The example below uses a RoleType
enum and a discriminator function. The Discriminate
function takes the reader
traverses the JSON once to find a discriminating property and returns what type of subclass the json is. Then a switch
statement on the result of this uses a standard JsonSerializer.Deserialize()
with the matching subclass type and a string version of the content from the reader. This makes things much easier as once the type of subclass has been ascertained the default deserialization code that comes with dotnet can be used. This is much less error prone and doesn’t require updates to the deserialzation patterns when the subclasses or base class change. Only new enum variants and discriminators need to be introduced. IMPORTANT: the GetReaderAsString
function does not have an example implementation here yet, but this would require you to traverse the reader and correctly push the properties with their JSON representation into a string, including "
and :
characters.
public enum RoleType
{
Doctor,
Dentist
}
public class RoleDeserializer : JsonConverter<Role>
{
public RoleType Discriminate(Utf8JsonReader reader)
{
while (reader.Reader())
{
if (reader.TokenType == JsonTokenType.PropertyName)
{
if (reader.GetString() == "HospitalId")
{
return RoleType.Doctor;
}
}
}
return RoleType.Dentist;
}
public override Role Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
// figure out what type of subclass this is
switch (Discriminate(reader))
{
// deserialize using a standard deserializer
case RoleType.Doctor:
return JsonSerializer.Deserialize<Doctor>(GetReaderAsString(ref reader));
case RoleType.Dentist:
return JsonSerializer.Deserialize<Dentist>(GetReaderAsString(ref reader));
}
return new Doctor();
}
public string GetReaderAsString(ref Utf8JsonReader reader)
{
// parse the reader into a string
}
// there will also be an unimplmeneted version of the Write function below
}