A colleague of mine shared with me an
interesting case on DataContractJsonSerializer
and DataContractSerializer
. The
difference between these two classes can be resumed roughly as “one works with JSON and the other one with XML”.
Brief description from MSDN :
Class name | Description |
---|---|
DataContractJsonSerializer | Serializes objects to the JavaScript Object Notation (JSON) and deserializes JSON data to objects. This class cannot be inherited. |
DataContractSerializer | Serializes and deserializes an instance of a type into an XML stream or document using a supplied data contract. This class cannot be inherited. |
The case consists of serializing and deserializing a model -which has a DateTime
field- using these two serializers and a memorystream. First let’s create a very simple model class:
Model class
public class Person
{
public string Name { get; set; }
public DateTime Birthday { get; set; }
public override string ToString()
{
return string.Format("{0} was born on {1}", Name, Birthday.ToString("yyyy-MM-dd hh:mm:ss"));
}
}
We’ll also need Serialize
and Deserialize
methods that are able to handle the model I defined above:
Serialize & Deserialize methods
static void Serialize(XmlObjectSerializer serializer, MemoryStream memoryStream, Person personToSerialize)
{
serializer.WriteObject(memoryStream, personToSerialize);
memoryStream.Position = 0;
}
static Person Deserialize(XmlObjectSerializer serializer, MemoryStream memoryStream)
{
var deserializedPerson = serializer.ReadObject(memoryStream) as Person;
return deserializedPerson;
}
Please note the type of serializer
parameter in both of the methods. XmlObjectSerializer
is the base class for both DataContractJsonSerializer
and DataContractSerializer
and they are defined like the following :
public sealed class DataContractJsonSerializer : XmlObjectSerializer {}
public sealed class DataContractSerializer : XmlObjectSerializer {}
public abstract class XmlObjectSerializer
{
public abstract void WriteStartObject(XmlDictionaryWriter writer, object graph);
public abstract void WriteObjectContent(XmlDictionaryWriter writer, object graph);
public abstract void WriteEndObject(XmlDictionaryWriter writer);
......
}
Whole picture
When we put it all together
static void Main(string[] args)
{
var serializer = new DataContractSerializer(typeof(Person));
var jesus = new Person { Name = "Jesus", Birthday = new DateTime(1, 1, 1, 0, 0, 0) };
Person deserializedJesus = null;
using (var memoryStream = new MemoryStream())
{
Serialize(serializer, memoryStream, jesus);
deserializedJesus = Deserialize(serializer, memoryStream);
}
Console.WriteLine(deserializedJesus);
}
and run the program. We get the following output :
Jesus was born on 0001-01-01 12:00:00
Press any key to continue . . .
That is fine, let’s change now the serializer from DataContractSerializer
to DataContractJsonSerializer
by replacing the following line
var serializer = new DataContractSerializer(typeof(Person));
by
var serializer = new DataContractJsonSerializer(typeof(Person));
We re-run the program and surprise!
Unhandled Exception: System.Runtime.Serialization.SerializationException: DateTime values that are greater than DateTime.MaxValue or smaller than DateTime.MinValue when converted to UTC cannot be serialized to JSON. ---> System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
Parameter name: value
--- End of inner exception stack trace ---
at System.Runtime.Serialization.Json.JsonWriterDelegator.WriteDateTimeInDefaultFormat(DateTime value)
at System.Runtime.Serialization.Json.JsonWriterDelegator.WriteDateTime(DateTime value)
at WritePersonToJson(XmlWriterDelegator , Object , XmlObjectSerializerWriteContextComplexJson , ClassDataContract , XmlDictionaryString[] )
at System.Runtime.Serialization.Json.JsonClassDataContract.WriteJsonValueCore(XmlWriterDelegator jsonWriter, Object obj, XmlObjectSerializerWriteContextComplexJson context, RuntimeTypeHandle declaredTypeHandle)
Visibly DataContractJsonSerializer
doesn’t like Jesus :) .
What happened here?
The error message is quite clear despite the fact that it is strangely formulated:
DateTime values that are greater than DateTime.MaxValue or smaller than DateTime.MinValue
when converted to UTC cannot be serialized to JSON.
At one point, DataContractJsonSerializer
takes the DateTime
value -Jesus’ birthday- we affected
and it tries to convert this one to UTC and the result is not within [DateTime.Min,DateTime.Max].
Let’s gather all the information we have :
- The time zone I am in is UTC + 1.
- DateTime.Min = 00:00:00.0000000, January 1, 0001.
- DateTime.Max = 23:59:59.9999999, December 31, 9999
So the code does the equivalent of new DateTime(1, 1, 1, 0, 0, 0).Add(-1)
which is lower than DateTime.Min
constant value hence the reason of the
exception we got.
How to fix it ?
The fix would be to directly assign UTC value to Jesus’ birthday by replacing the following line
Birthday = new DateTime(1, 1, 1, 0, 0, 0)
with
Birthday = DateTime.SpecifyKind(new DateTime(1, 1, 1, 0, 0, 0), DateTimeKind.Utc)
And when we run the program, we get :
Jesus was born on 0001-01-01 12:00:00
Press any key to continue . . .
Why this bug is a sneaky ?
Because depending on the time zone you are in, you may or not encounter the bug. If you are in UTC+0, the program will work just fine for you with both of the serializers.
Important notes
- The source code for this article is available on GitHub
-
Do not use the code snippets given in this article in production code. You might want to add guard clauses against null or unwanted values and add unit tests with edge cases - we worked on lower bound (
DateTime.Min
), what happens on upper bound (DateTime.Max
)? - . - All credits for the time zone map belongs to wikipedia user TimeZonesBoy. The map itself is borrowed from wikipedia