Why IsoDateTimeConverter is not used to serialize Dictionary<DateTime, int> - c#

The result of a webAPI method that return a Dictionary is not serialized with the format defined in IsoDateTimeConverter
This is my configuration:
public static class WebApiConfig
{
//Web API configuration and services
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Formatters.JsonFormatter.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc;
config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new IsoDateTimeConverter
{
DateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fff'Z'"
});
}
}
Here a sample of webAPI method
[Route("GetPlanning")]
[HttpPost]
public Dictionary<DateTime, IEnumerable<int>> GetPlanning()
{
Dictionary<DateTime, IEnumerable<int>> planning = new Dictionary<DateTime,IEnumerable<int>>();
planning.Add(DateTime.UtcNow, new List<int>(){0,1,2});
return planning;
}
In client side the result is an object with a property that is a date without the millisecond.
If i return a array : return planning.ToArray(); the result is an array of object key value with a date with millisecond.
So why the format is apllied on a date in a array and not in a dictionary?

The reason IsoDateTimeConverter is not used for dictionary keys is that Json.NET does not serialize the keys - it merely converts them to strings. From the docs:
When serializing a dictionary, the keys of the dictionary are converted to strings and used as the JSON object property names. The string written for a key can be customized by either overriding ToString() for the key type or by implementing a TypeConverter. A TypeConverter will also support converting a custom string back again when deserializing a dictionary.
Thus converters, including IsoDateTimeConverter, are not used when converting dictionary keys to JSON.
That being said, IsoDateTimeConverter is no longer necessary. From Serializing Dates in JSON
From Json.NET 4.5 and onward dates are written using the ISO 8601 format by default, and using this converter is unnecessary.
The same date formatting can be obtained by setting JsonSerializerSettings.DateFormatString:
config.Formatters.JsonFormatter.SerializerSettings.DateFormatString = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fff'Z'";
But how does this affect dictionary key conversion to JSON? As it turns out, Json.NET does respect this setting when converting a DateTime to a string as a dictionary key, as shown in the reference source. Thus:
var time = DateTime.Now;
Dictionary<DateTime, IEnumerable<int>> planning = new Dictionary<DateTime, IEnumerable<int>>();
planning.Add(DateTime.UtcNow, new List<int>() { 0, 1, 2 });
var root = new { today = time, planning = planning };
var settings = new JsonSerializerSettings
{
DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc,
DateFormatString = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fff'Z'",
};
var json = JsonConvert.SerializeObject(root, Formatting.Indented, settings);
Console.WriteLine(json);
Produces the required output with consistent formatting for a DateTime value used as a dictionary keys and used as a property:
{
"today": "2016-09-09T03:54:51.704Z",
"planning": {
"2016-09-09T03:54:51.704Z": [
0,
1,
2
]
}
}

Related

How to deserialize JSON (snake_case) to dynamic (PascalCase)?

This code works fine:
string json = #"{""MyProperty"" : ""bar""}";
var payload = JsonConvert.DeserializeObject<dynamic>(json);
string typedProperty = payload.MyProperty; //contains "bar"
Let's try to do the same with snake_case JSON. We add a SnakeCaseNamingStrategy which is actually a recommended way to handle snake_case.
_snakeSettings = new JsonSerializerSettings()
{
ContractResolver = new UnderscorePropertyNamesContractResolver()
};
public class UnderscorePropertyNamesContractResolver : DefaultContractResolver
{
public UnderscorePropertyNamesContractResolver()
{
NamingStrategy = new SnakeCaseNamingStrategy();
}
}
Then, apply the settings to the DeserializeObject call. In case of deserialization to a static type, those settings are successfully applied to snake_case JSON:
string snakeJson = #"{""my_property"" : ""bar""}";
var payload = JsonConvert.DeserializeObject<Payload>(snakeJson, _snakeSettings);
string typedProperty = payload.MyProperty; //contains "bar"
OK, switch the target type to dynamic:
var payload = JsonConvert.DeserializeObject<dynamic>(snakeJson, _snakeSettings);
string typedProperty = payload.MyProperty; //is null
string wrongProperty = payload.my_property; //is not null
As you see, _snakeSettings are ignored this time. I guess this is a bug. Does any workaround exist to do JSON (snake_case) -> dynamic (PascalCase) deserialization?
Environment:
<TargetFramework>netcoreapp1.1</TargetFramework>
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
When you call JsonConvert.DeserializeObject<dynamic> it acts the same way as JsonConvert.DeserializeObject<JObject>. JObject is not a real result of the deserialization, but some intermediate state of your data, It is closer to readers than to objects. E.g. it allows you to deserialize only part of a JSON
So seems like JsonConvert.DeserializeObject<dynamic> creates not a result object but a reach, functional reader for the JSON data. I suppose, that's why it shows you data as it was without any post processing
I suppose it's better to direct that question to a "Newtonsoft.Json" developers.

Using Json.net to deserialize to any unkown type works for objects but not value types

I've implemented a Json Serializer based on Json.net to accept any object type and serialize it (for placement into my cache)
The cache interface doesn't allow me to speficy the type, so When I retrieve from the cache I need to dynamically create the type from the meta information.
Which works well for objects, the problem I am now facing is that i doesn't work for value types, I will get an exception saying something along the lines of cannot cast JValue to JObject.
My question is how can I cater for value types as well as object types? It would be great if there was a TryParse for a JObject, which I could write myself, but feel like I am going down a rabbit hole?
What is the best way to achieve this?
My code is as follows, settings for Json.net:
_settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
NullValueHandling = NullValueHandling.Ignore,
DateTimeZoneHandling = DateTimeZoneHandling.Utc,
TypeNameHandling = TypeNameHandling.All
};
_settings.Converters.Add(new StringEnumConverter());
The set function (serialize):
public void Put(string cacheKey, object toBeCached, TimeSpan cacheDuration)
{
_cache.Set(cacheKey, JsonConvert.SerializeObject(toBeCached, _settings), cacheDuration);
}
And the get (deserialize):
public object Get(string cacheKey)
{
try
{
var value = _cache.Get(cacheKey);
if (!value.HasValue)
{
return null;
}
var jobject = JsonConvert.DeserializeObject<JObject>(value);
var typeName = jobject?["$type"].ToString();
if (typeName == null)
{
return null;
}
var type = Type.GetType(typeName);
return jobject.ToObject(type);
}
catch (Exception e)
{
// Todo
return null;
}
}
You need to parse to a JToken rather than a JObject, then check to see if the returned type is a JValue containing a JSON primitive:
public static object Get(string value)
{
var jToken = JsonConvert.DeserializeObject<JToken>(value);
if (jToken == null)
return null;
else if (jToken is JValue)
{
return ((JValue)jToken).Value;
}
else
{
if (jToken["$type"] == null)
return null;
// Use the same serializer settings as used during serialization.
// Ideally with a proper SerializationBinder that sanitizes incoming types as suggested
// in https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_TypeNameHandling.htm
var _settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
NullValueHandling = NullValueHandling.Ignore,
DateTimeZoneHandling = DateTimeZoneHandling.Utc,
TypeNameHandling = TypeNameHandling.All,
Converters = { new StringEnumConverter() },
//SerializationBinder = new SafeSerializationBinder(),
};
// Since the JSON contains a $type parameter and TypeNameHandling is enabled, if we deserialize
// to type object the $type information will be used to determine the actual type, using Json.NET's
// serialization binder: https://www.newtonsoft.com/json/help/html/SerializeSerializationBinder.htm
return jToken.ToObject(typeof(object), JsonSerializer.CreateDefault(_settings));
}
}
Note, however, that type information for primitives will not get precisely round-tripped:
Integers will be returned as a long (or a BigInteger if larger than long.MaxValue).
Floating point values will be returned as a double or a decimal, depending upon the setting of JsonSerializerSettings.FloatParseHandling -- either FloatParseHandling.Double or FloatParseHandling.Decimal.
JSON strings that "look like" dates will get converted to DateTime, DateTimeOffset, or left as strings, depending on the setting JsonSerializerSettings.DateParseHandling.
enum names will be converted to strings.
If you need to round-trip type information for primitives, consider using TypeWrapper<T> from Deserialize specific enum into system.enum in Json.Net to encapsulate your root objects.
Finally, if there is any chance you might be deserializing untrusted JSON (and if you are deserializing from a file or from the internet then you certainly are), please note the following caution from the Json.NET documentation:
TypeNameHandling should be used with caution when your application deserializes JSON from an external source. Incoming types should be validated with a custom SerializationBinder when deserializing with a value other than None.
For a discussion of why this may be necessary, see TypeNameHandling caution in Newtonsoft Json, How to configure Json.NET to create a vulnerable web API, and Alvaro Muñoz & Oleksandr Mirosh's blackhat paper https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf

Serialize hashtable using Json.Net

I have a hashtable whose keys are of type integer, however when deserializing using json.net the keys come back as strings, is there a way to keep the key type on hashtable using json.net serialization/deserialization? This hashtable is a property of the type 'MyType'
var settings = new JsonSerializerSettings();
settings.TypeNameHandling = TypeNameHandling.Objects;
string json = JsonConvert.SerializeObject(o, Formatting.Indented, settings);
mo = JsonConvert.DeserializeObject<MyType>(json, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Objects });
public Hashtable jsonViews
{
get { return mViews; }
set { mViews = value; }
}
The problem is that the System.Collections.Hashtable isn't strongly typed - it will hold any type of object, and JSON.NET is most likely serialising the string representation of your hashtable contents.
Before you spend too much time massaging the JSON.NET serializer/deserializer to compensate for this, you might want to consider switching your Hashtable out for a Dictionary<TKey, TValue>. It's almost identical in terms of performance, but gives you the advantage and safety of a strongly-typed collection.
A strongly-typed collection will resolve your Json.NET deserialization issues, as Json.NET can infer the type from the dictionary.
The use of Dictionary<TKey,TValue> over Hashtable is discussed here.
Here's a generic static extension method that I wrote to help me with this problem. I basically wanted this code to never blow up even if the data is somehow corrupted.
public static T Read<T>(this Hashtable hash, string key)
{
if (!hash.ContainsKey(key))
return default;
if (hash[key] is T)
return (T)hash[key];
if (hash[key] is JToken token)
return token.ToObject<T>();
try
{
return (T)Convert.ChangeType(hash[key], typeof(T));
}
catch (InvalidCastException)
{
Debug.LogWarning($"{key} had the wrong type of {hash[key].GetType()}");
return default;
}
}

Servicestack serialization

For example I have this object
JsonObject o = new JsonObject();
o.Add("k1","0123");
o.Add("k2","123.");
When I serialize this json object the result is wrong:
{
"k1":0123,"k2":123.
}
It assumes that, those are numbers. Whereas these are incorrect numbers.
We have problems deserializing them on client side.
The correct value should be like below:
{
"k1":"0123","k2":"123."
}
How can I achieve this?
How to disable detecting numbers at all???
Below configurations did not help, I think they are for other purposes
JsConfig.TryToParseNumericType = false;
JsConfig.TryToParsePrimitiveTypeValues = false;
JsonObject class derives from Dictionary<string, string> so I don't think there is much you can do with that. You can create an instance of Dictionary<string, object> instead.
[Test]
public void SerializerTest()
{
Dictionary<string, object> o = new Dictionary<string, object>();
o.Add("k1", "0123");
o.Add("k2", "123.");
Assert.AreEqual("{\"k1\":\"0123\",\"k2\":\"123.\"}",
JsonSerializer.SerializeToString(o));
}
I would recommend you use the JsonConvert class, which handles JSON really well.
Usage would be as follows:
var serialized = JsonConvert.SerializeObject(new { k1 = "0123", k2 = "123" });
dynamic deserialized = JsonConvert.DeserializeObject(serialized);
Assert.That(deserialized.k1.ToString(), Is.EqualTo("0123")); // deserialized.k1 will be an object of 'JValue', so need to call toString()

Problems with Json Serialize Dictionary<Enum, Int32>

whenever i try to serialize the dictionary i get the exception:
System.ArgumentException: Type
'System.Collections.Generic.Dictionary`2[[Foo.DictionarySerializationTest+TestEnum, Foo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null],[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]'
is not supported for serialization/deserialization of a dictionary,
keys must be strings or object
My Testcase is:
public class DictionarySerializationTest
{
public enum TestEnum { A, B, C }
//tried with numbers, too: public enum TestEnum { A = 1, B = 2, C = 3 }
public void SerializationTest()
{
Dictionary<TestEnum, Int32> data = new Dictionary<TestEnum, Int32>();
data.Add(TestEnum.A, 1);
data.Add(TestEnum.B, 2);
data.Add(TestEnum.C, 3);
JavaScriptSerializer serializer = new JavaScriptSerializer();
String result = serializer.Serialize(data);
// Throws
}
public void SerializationToObjectTest()
{
Dictionary<object, Int32> data = new Dictionary<object, Int32>();
data.Add(Enum.ToObject(typeof(TestEnum), TestEnum.A), 1);
data.Add(Enum.ToObject(typeof(TestEnum), TestEnum.B), 2);
data.Add(Enum.ToObject(typeof(TestEnum), TestEnum.C), 3);
JavaScriptSerializer serializer = new JavaScriptSerializer();
String result = serializer.Serialize(data);
// Throws
}
public void SerializationStringTest()
{
Dictionary<String, Int32> data = new Dictionary<String, Int32>();
data.Add(TestEnum.A.ToString(), 1);
data.Add(TestEnum.B.ToString(), 2);
data.Add(TestEnum.C.ToString(), 3);
JavaScriptSerializer serializer = new JavaScriptSerializer();
String result = serializer.Serialize(data);
// Succeeds
}
}
Of course i could use .ToString() whenever i enter something into the Dictionary but since it's used quite often in performance relevant methods i would prefer using the enum.
My only solution is using .ToString() and converting before entering the performance critical regions but that is clumsy and i would have to change my code structure just to be able to serialize the data.
Does anyone have an idea how i could serialize the dictionary as <Enum, Int32>?
I use the System.Web.Script.Serialization.JavaScriptSerializer for serialization.
UPDATE:
I switched to Dictionary<String, Int32> now and it works but i hope someone shows a solution as i don't really like using strings in place of a type safe enum.
I know it's late, but maybe someone else can make use of it in the future. You can achieve what you need using LINQ:
Dictionary<TestEnum, Int32> data = new Dictionary<TestEnum, Int32>();
data.Add(TestEnum.A, 1);
data.Add(TestEnum.B, 2);
data.Add(TestEnum.C, 3);
JavaScriptSerializer serializer = new JavaScriptSerializer();
Dictionary<string, Int32> dataToSerialize = data.Keys.ToDictionary(p => p.ToString(), p => data[p]);
string dataSerialized = serializer.Serialize(dataToSerialize);
Use Newtonsoft (Newtonsoft.Json.dll) to serialize the Dictionary object and you'd be fine. It's a popular third party library you would have to download and include in your project as a reference.
See example below:
var _validationInfos = new Dictionary<ImportField, ValidationInfo>();
var serializedData = JsonConvert.SerializeObject(_validationInfos);
I think that you are having trouble because TestEnum is declared as a private enum. Try marking it as a public enum. The serializer needs to be able to find your enum via reflection in order to serialize it.
Also according to the Docs, the enums must have integer values. So you might want to write:
public enum TestEnum { A = 1, B = 2, C =3 }
Also, the docs say that this enum will just get mapped to its corresponding integer value during the serialization. So depending on what you are doing on the other end, Strings might be more expressive and easier to work with.
Exception says "keys must be strings or object" so try
data.Add(Enum.ToObject(typeof(TestEnum), TestEnum.A));
data.Add(Enum.ToObject(typeof(TestEnum), TestEnum.B));
data.Add(Enum.ToObject(typeof(TestEnum), TestEnum.C));
I did not test it though, just a guess.
I’ve created JavaScriptSerializer extension DeserializeDictionary- see http://geekswithblogs.net/mnf/archive/2011/06/03/javascriptserializer-extension-deserializedictionarytkey-tvalue.aspx
public static Dictionary<TKey, TValue> DeserializeDictionary<TKey, TValue>(this JavaScriptSerializer jss, string jsonText)
{
var dictWithStringKey = jss.Deserialize<Dictionary<string,TValue>>(jsonText);
var dict=dictWithStringKey.ToDictionary(de => jss.ConvertToType<TKey>(de.Key),de => de.Value);
return dict;
}

Categories