Reputation: 11680
I have a class that has a field of type Object that may contain a TimeSpan object.
I am serializing this and the deserializing this:
public class MyObject
{
public object myTimeSpan { get; set; }
}
...
var myObject = new MyObject { myTimeSpan = TimeSpan.FromDays(2) };
var json = JsonConvert.SerializeObject(myObject);
...
var duplicateObject = JsonConvert.DeserializeObject<MyObject>(json);
And when I do, duplicateObject.myTimeSpan contains the string "2:00:00".
How can I get this to deserialize into a TimeSpan object?
Two points:
Upvotes: 3
Views: 22823
Reputation: 129667
Json.Net has a TypeNameHandling
setting for dealing with unknown types such that they can be deserialized properly. When this setting is turned on, it causes Json.Net to insert special $type
properties into the JSON which are then used as hints when deserializing. Unfortunately, this setting does not seem to work with "simple" types such as TimeSpan
, because their values are serialized as strings rather than objects.
To work around this issue, I would suggest making a custom JsonConverter
that uses the same idea. Instead of outputting the string value of the object directly, the converter would output a sub-object representation with two properties: type
and value
. The type
property of the sub-object would contain the assembly-qualified type name for the object, while the value
property would contain the actual serialized value. On deserialization, the converter would look for the type
property to know what type of object to instantiate from the value property. The nice thing about this approach is you do not have to add any additional properties or logic to your model class(es).
Here is how it might look in code:
class UnknownObjectConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JObject jo = new JObject();
jo["type"] = value.GetType().AssemblyQualifiedName;
jo["value"] = JToken.FromObject(value, serializer);
jo.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
Type type = Type.GetType(jo["type"].ToString(), throwOnError: true);
return jo["value"].ToObject(type, serializer);
}
}
To use the converter, just decorate any object
properties in your classes that need special handling with a [JsonConverter]
attribute like this:
public class MyObject
{
[JsonConverter(typeof(UnknownObjectConverter))]
public object MyValue { get; set; }
}
Here is a round-trip demo which shows how this can work for several different types.
class Program
{
static void Main(string[] args)
{
List<MyObject> list = new List<MyObject>
{
new MyObject { MyValue = TimeSpan.FromDays(2) },
new MyObject { MyValue = "foo" },
new MyObject { MyValue = new DateTime(2014, 12, 20, 17, 06, 44) },
new MyObject { MyValue = new Tuple<int, bool>(23, true) }
};
string json = JsonConvert.SerializeObject(list, Formatting.Indented);
Console.WriteLine(json);
Console.WriteLine();
list = JsonConvert.DeserializeObject<List<MyObject>>(json);
foreach (MyObject obj in list)
{
Console.WriteLine(obj.MyValue.GetType().Name + ": " + obj.MyValue.ToString());
}
}
}
Here is the output:
[
{
"MyValue": {
"type": "System.TimeSpan, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"value": "2.00:00:00"
}
},
{
"MyValue": {
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"value": "foo"
}
},
{
"MyValue": {
"type": "System.DateTime, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"value": "2014-12-20T17:06:44"
}
},
{
"MyValue": {
"type": "System.Tuple`2[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"value": {
"Item1": 23,
"Item2": true
}
}
}
]
TimeSpan: 2.00:00:00
String: foo
DateTime: 12/20/2014 5:06:44 PM
Tuple`2: (23, True)
Upvotes: 9