Serialize hashtable using Json.Net - c#

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;
}
}

Related

Newtonsoft JsonConvert vs System.Web.Helpers Json - type cast problem

I've been using System.Web.Helpers.Json to deserialize objects quite successfully up until I received a json with the keys that only differ in case of the letters and the Decode() method throws an ArgumentException. I tried to figure out how to make this class work in case-sensitive way and couldn't, so I decided to go with Newtonsoft library instead. Json.NET works fine case-wise, however the deserialized objects it returns would require a type cast as follows:
[TestMethod]
public void CaseSensitivityTest() {
string json = "{\"e\":\"executionReport\",\"E\":1616877261436}";
dynamic result = Newtonsoft.Json.JsonConvert.DeserializeObject(json);
string executionReport = result.e;//have to assign to a typed variable for the assert below to work
Assert.AreEqual("executionReport", executionReport);
Assert.IsTrue(1616877261436 == (long)result.E);//or explicitly cast to a type
result = System.Web.Helpers.Json.Decode(json);//System.ArgumentException: An item with the same key has already been added.
Assert.IsTrue(1616877261436 == result.E);//this would've worked without any type cast as in the example below
}
The rest of my code relies heavily on deserialized objects having properly typed properties (e.g. my typical code decimal.Parse(deserializedResponse.price) expects price to be string and not JValue<string>). Here's another comparison:
[TestMethod]
public void TypeCastTest() {
string json = "{\"intValue\":123}";
dynamic webHelpersResult = System.Web.Helpers.Json.Decode(json);
dynamic newtonSoftResult = Newtonsoft.Json.JsonConvert.DeserializeObject(json);
Assert.AreEqual(123, webHelpersResult.intValue);//All good here, I want JsonConvert to work the same way
Assert.AreEqual(123, newtonSoftResult.intValue);//Assert.AreEqual failed. Expected:<123 (System.Int32)>. Actual:<123 (Newtonsoft.Json.Linq.JValue)>.
}
It would be very difficult to refactor adding type casts everywhere, so I would prefer a single point of fix. I need either to make the System.Web.Helpers.Json case-sensitive or Newtonsoft.Json.JsonConvert to return .NET-typed values and not of JValue type. What would be the best way to achieve this? I'm writing a console application running on Windows 7 machine, so all fancy web/WINRT/Xamarin/etc stuff is not always available.
UPDATE
the suggestion of deserializing into ExpandoObject as below:
dynamic newtonSoftResult = JsonConvert.DeserializeObject<ExpandoObject>(json);
seems to work initially, however it fails to deserialize json lists and I couldn't make it backward compatible with System.Web.Helpers.Json.Decode() result:
string single = "{\"s\":\"String1\",\"f\":\"0.00\"}";
string multiple = "[{\"s\":\"String1\",\"f\":\"0.00\"},{\"s\":\"String2\",\"f\":\"1.23\"}]";
var helpersSingle = System.Web.Helpers.Json.Decode(single);
var helpersMultiple = System.Web.Helpers.Json.Decode(multiple);
var newtonSingle = Newtonsoft.Json.JsonConvert.DeserializeObject<ExpandoObject>(single);
var newtonMultiple = JsonConvert.DeserializeObject<ExpandoObject>(multiple);//System.InvalidCastException: Unable to cast object of type 'System.Collections.Generic.List`1[System.Object]' to type 'System.Dynamic.ExpandoObject'.
Assert.AreEqual("String1", helpersSingle.s);
Assert.AreEqual("String2", helpersMultiple[1].s);
Assert.IsFalse(helpersSingle is IEnumerable);
Assert.IsFalse(newtonSingle is IEnumerable);//This fails as well as ExpandoObject would implement IEnumerable for its properties
You can use Newtonsoft.Json with the following helper class:
public static class JsonHelper
{
public static object Deserialize(string json)
{
return ToObject(JToken.Parse(json));
}
private static object ToObject(JToken token)
{
switch (token.Type)
{
case JTokenType.Object:
var expando = new ExpandoObject() as IDictionary<string, object>;
foreach (JProperty prop in token.Children<JProperty>())
{
expando.Add(prop.Name, ToObject(prop.Value));
}
return expando;
case JTokenType.Array:
return token.Select(ToObject).ToList();
default:
return ((JValue)token).Value;
}
}
}
In your tests you can do:
dynamic result = JsonHelper.Deserialize(json);
The result will either be an ExpandoObject or a List<ExpandoObject> which should work with most of your tests. You will have to make an adjustment for tests that check for IEnumerable since ExpandoObject does implement this interface. If you need to differentiate between a single object or multiple, you could check for IList instead.
Working example here: https://dotnetfiddle.net/n2jI1d
Newtonsoft wraps the JSON Properties.. for various very good reasons I wont get into. You don't need to cast you can just use .Value i.e newtonSoft.intValue.Value. I should point out that you are still parsing the entire JSON string here using dynamic types because you don't use most of it isn't a good excuse to use dynamic typing. I would highly recommend you not use dynamic types but to each there own.
[TestMethod]
public void TypeCastTest() {
string json = "{\"intValue\":123}";
dynamic webHelpersResult = System.Web.Helpers.Json.Decode(json);
dynamic newtonSoftResult = Newtonsoft.Json.JsonConvert.DeserializeObject(json);
Assert.AreEqual(123, webHelpersResult.intValue);
Assert.AreEqual(123, newtonSoftResult.intValue.Value);
}

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

Referring to dynamic members of C# 'dynamic' object

I'm using JSON.NET to deserialize a JSON file to a dynamic object in C#.
Inside a method, I would like to pass in a string and refer to that specified attribute in the dynamic object.
For example:
public void Update(string Key, string Value)
{
File.Key = Value;
}
Where File is the dynamic object, and Key is the string that gets passed in. Say I'd like to pass in the key "foo" and a value of "bar", I would do:
Update("foo", "bar");, however due to the nature of the dynamic object type, this results in
{
"Key":"bar"
}
As opposed to:
{
"foo":"bar"
}
Is it possible to do what I'm asking here with the dynamic object?
I suspect you could use:
public void Update(string key, string Value)
{
File[key] = Value;
}
That depends on how the dynamic object implements indexing, but if this is a Json.NET JObject or similar, I'd expect that to work. It's important to understand that it's not guaranteed to work for general dynamic expressions though.
If you only ever actually need this sort of operation (at least within the class) you might consider using JObject as the field type, and then just exposing it as dynamic when you need to.
Okay so it turns out I'm special. Here's the answer for those that may stumble across this in future,
Turns out you can just use the key like an array index and it works perfectly. So:
File[Key] = Value; Works the way I need as opposed to
File.Key = Value;
Thanks anyway!
You can do it, if you're using JObject from JSON.NET. It does not work with an ExpandoObject.
Example:
void Main()
{
var j = new Newtonsoft.Json.Linq.JObject();
var key = "myKey";
var value = "Hello World!";
j[key] = value;
Console.WriteLine(j["myKey"]);
}
This simple example prints "Hello World!" as expected. Hence
var File = new Newtonsoft.Json.Linq.JObject();
public void Update(string key, string Value)
{
File[key] = Value;
}
does what you expect. If you would declare File in the example above as
dynamic File = new ExpandoObject();
you would get a runtime error:
CS0021 Cannot apply indexing with [] to an expression of type 'ExpandoObject'

Deserializing a json string to an object array and then cast it to Dictionary?

This is the json string
[{"ProductIdentifier":"{test:'some data'}","validationCompleted":0}]
I am trying to deserialize an object into an object array which works fine but when i am trying to cast it back to an dictionary i am getting an exception can anyone please help me with this thank you.
m_receiptsList is an object[]
m_receiptsList = JsonConvert.DeserializeObject<object[]>(pastJsonString,
new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All});
for(int i=0; i<m_receiptsList.Length; i++)
{
Dictionary<string, object> receiptItem = (Dictionary<string, object>)m_receiptsList[i];
bool breceipt = receiptItem["validationCompleted"].Equals(0);
}
// exception while doing this saying cannot cast object m_receiptList[i] to dictionary.
If you want Json.NET to deserialize your JSON as an array of dictionaries, you need to tell it to do so:
object[] m_receiptsList;
m_receiptsList = JsonConvert.DeserializeObject<Dictionary<string, object>[]>(pastJsonString, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All });
Incidentally, setting TypeNameHandling = TypeNameHandling.All isn't going to help you because your JSON sample has no type information. If it did, there would be a property "$type" at the beginning of your object(s).
Also, JSON.Net will convert a JSON integer into a long unless told otherwise, so you would need to do:
foreach (IDictionary<string, object> dict in m_receiptsList)
{
bool breceipt = dict["validationCompleted"].Equals((long)0);
}

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