Last active
April 29, 2018 11:11
-
-
Save borland/102a44fd14f7cdf2376c22db05d15cce to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Newtonsoft.Json; | |
using Newtonsoft.Json.Linq; | |
using System; | |
using System.Collections.Generic; | |
public class Program | |
{ | |
static readonly string jsonExample = @" | |
{ | |
""intValue"": 1, | |
""stringValue"": ""a string"", | |
""looksLikeInt"": ""12"", | |
""looksLikeDate"": ""2018-04-29T22:20:01Z"", | |
""shouldBeDate"": ""2018-04-29T22:20:01Z"" | |
} | |
"; | |
class RawObject | |
{ | |
public int IntValue { get; set; } | |
public string StringValue { get; set; } | |
public string LooksLikeInt { get; set; } | |
public string LooksLikeDate { get; set; } | |
public string ShouldBeDate { get; set; } | |
public override string ToString() => $"[RawObject IntValue={IntValue} StringValue=\"{StringValue}\" LooksLikeInt=\"{LooksLikeInt}\" LooksLikeDate=\"{LooksLikeDate}\" ShouldBeDate=\"{ShouldBeDate}\"]"; | |
} | |
class CoerceObject | |
{ | |
public int IntValue { get; set; } | |
public string StringValue { get; set; } | |
public int LooksLikeInt { get; set; } | |
public DateTime LooksLikeDate { get; set; } | |
public DateTime ShouldBeDate { get; set; } | |
public override string ToString() => $"[CoerceObject IntValue={IntValue} StringValue=\"{StringValue}\" LooksLikeInt={LooksLikeInt} LooksLikeDate={LooksLikeDate} ShouldBeDate={ShouldBeDate}]"; | |
} | |
class ExtensionObject | |
{ | |
public int IntValue { get; set; } | |
public string StringValue { get; set; } | |
public DateTime ShouldBeDate { get; set; } | |
[JsonExtensionData] | |
public Dictionary<string, JToken> Other { get; set; } | |
} | |
[JsonConverter(typeof(ExtensionObjectNoDateTimeGuessing.Converter))] | |
class ExtensionObjectNoDateTimeGuessing | |
{ | |
public int IntValue { get; set; } | |
public string StringValue { get; set; } | |
public DateTime ShouldBeDate { get; set; } | |
[JsonExtensionData] | |
public JObject Other { get; set; } | |
class Converter : JsonConverter | |
{ | |
public override bool CanConvert(Type objectType) => throw new NotImplementedException(); | |
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) | |
{ | |
if (reader.TokenType == JsonToken.Null) | |
return null; | |
var old = reader.DateParseHandling; | |
try { | |
reader.DateParseHandling = DateParseHandling.None; | |
existingValue = existingValue ?? serializer.ContractResolver.ResolveContract(objectType).DefaultCreator(); | |
serializer.Populate(reader, existingValue); | |
return existingValue; | |
} | |
finally { | |
reader.DateParseHandling = old; | |
} | |
} | |
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException(); | |
} | |
} | |
public static void Main(string[] args) | |
{ | |
// GOOD: Json de-serialises correctly to whatever type you tell it to | |
var rawObject = JsonConvert.DeserializeObject<RawObject>(jsonExample); | |
Console.WriteLine(rawObject); | |
// GOOD: Json de-serialises correctly to whatever type you tell it to. | |
// It will coerce strings to ints and DateTimes, but we've told it to do that so fair enough | |
var coerceObject = JsonConvert.DeserializeObject<CoerceObject>(jsonExample); | |
Console.WriteLine(coerceObject); | |
// OK: Everything's a string, but then we have to do the DateTime.parse ourselves which is no fun | |
var stringStringDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(jsonExample); | |
Console.WriteLine(stringStringDict); | |
// BAD: Json.Net guesses that our string looks like a DateTime and mishandles it | |
var stringObjDict = JsonConvert.DeserializeObject<Dictionary<string, object>>(jsonExample); | |
Console.WriteLine(stringObjDict); | |
// BAD: Asking for JObject or JToken is no better than Dictionary<string, object> Json.Net guesses that our string looks like a DateTime and mishandles it | |
// BEFORE we get a chance to deal with it | |
var extObj = JsonConvert.DeserializeObject<ExtensionObject>(jsonExample); | |
Console.WriteLine(extObj); | |
// BAD: If the root is JObject, Json.NET still guesses before it gives it to us | |
var jobject = JsonConvert.DeserializeObject<JObject>(jsonExample); | |
var joDate = jobject["looksLikeDate"].Value<DateTime>(); | |
var joString = jobject["looksLikeDate"].Value<string>(); | |
Console.WriteLine(jobject); | |
// GOOD: Explicit serializer settings telling it not to guess. If we ask for a DateTime it will kindly give us one though | |
var jobject2 = JsonConvert.DeserializeObject<JObject>(jsonExample, new JsonSerializerSettings { DateParseHandling = DateParseHandling.None }); | |
var joDate2 = jobject2["looksLikeDate"].Value<DateTime>(); | |
var joString2 = jobject2["looksLikeDate"].Value<string>(); | |
Console.WriteLine(jobject2); | |
// BEST: Custom JsonConverter switches off DateTime guessing, however if it's using reflection to match against an explict C# DateTime field it does the conversion | |
var extObj2 = JsonConvert.DeserializeObject<ExtensionObjectNoDateTimeGuessing>(jsonExample); | |
var extObj2Date = extObj2.Other["looksLikeDate"].Value<DateTime>(); | |
var extObj2String = extObj2.Other["looksLikeDate"].Value<string>(); | |
Console.WriteLine(extObj2); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment