Deserializing JSON to a Dictionary<string, Item> with Item being abstract - c#

I'm looking to deserialize a JSON string to a Dictionary<string, Item> with Item being an abstract class. I serialize many types of items, some being Weapons, some being Armour, Consumables, etc.
Error: Newtonsoft.Json.JsonSerializationException: 'Could not create an instance of type Item. Type is an interface or abstract class and cannot be instantiated.
EDIT: I'm using Newtonsoft.Json for serializing / deserializing
Deserialization code:
public Dictionary<string, Item> LoadItemDictionary()
{
Dictionary<string, Item> items = new Dictionary<string, Item>();
using (var reader = new StreamReader(ITEM_DICTIONARY_PATH, new UTF8Encoding(false)))
{
string json = reader.ReadToEnd();
items = JsonConvert.DeserializeObject<Dictionary<string, Item>>(json);
}
return items;
}
JSON code:
{
"excalibur": {
"damage": 9999,
"critChance": 10,
"itemID": "excalibur",
"iconLink": "",
"name": "Excalibur",
"description": "placeholder",
"itemType": 1,
"rarity": 4,
"stackSize": 1,
"canBeSold": false,
"buyPrice": 0,
"sellPrice": 0
}
}
Item class:
public abstract class Item
{
public string iconLink = string.Empty;
public string name = string.Empty;
public string description = string.Empty;
public ItemType itemType = ItemType.NONE;
public ItemRarity rarity = ItemRarity.COMMON;
public int stackSize = 1;
public bool canBeSold = false;
public int buyPrice = 0;
public int sellPrice = 0;
public enum ItemRarity
{
COMMON,
UNCOMMON,
RARE,
MYTHIC,
LEGENDARY,
}
public enum ItemType
{
NONE,
WEAPON,
ARMOUR,
CONSUMABLE,
}
}
Weapon Example:
public class Weapon : Item
{
public int damage = 0;
public int critChance = 0;
public new ItemType itemType = ItemType.WEAPON;
}

You can use custom converter to be able to deserialize to different types in same hierarchy. Also I highly recommend using properties instead of fields. So small reproducer can look like this:
public abstract class Item
{
public virtual ItemType Type => ItemType.NONE; // expression-bodied property
public enum ItemType
{
NONE,
WEAPON,
ARMOUR,
CONSUMABLE,
}
}
public class Weapon : Item
{
public override ItemType Type => ItemType.WEAPON; // expression-bodied property
public string SomeWeaponProperty { get; set; }
}
Custom converter:
public class ItemConverter : JsonConverter<Item>
{
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, Item? value, JsonSerializer serializer) => throw new NotImplementedException();
public override Item ReadJson(JsonReader reader, Type objectType, Item existingValue, bool hasExistingValue, JsonSerializer serializer)
{
// TODO handle nulls
var jObject = JObject.Load(reader);
Item result;
switch (jObject.GetValue("type", StringComparison.InvariantCultureIgnoreCase).ToObject<Item.ItemType>(serializer))
{
case Item.ItemType.WEAPON:
result = jObject.ToObject<Weapon>();
break;
// handle other types
// case Item.ItemType.ARMOUR:
// case Item.ItemType.CONSUMABLE:
case Item.ItemType.NONE:
default:
throw new ArgumentOutOfRangeException();
}
return result;
}
}
and example usage (or mark Item with JsonConverterAttribute):
var item = new Weapon();
var settings = new JsonSerializerSettings
{
Converters = { new ItemConverter() }
};
string json = JsonConvert.SerializeObject(item, settings);
var res = JsonConvert.DeserializeObject<Item>(json, settings);
Console.WriteLine(res.GetType()); // prints Weapon

Based on Op's comments, after modifying Item to non-abstract class, you can use a converter to convert to various derived classes as follows:
public class ItemCreationConvertor : JsonConverter
{
private readonly Dictionary<Item.ItemType, Type> map = new Dictionary<Item.ItemType, Type>
{
{Item.ItemType.WEAPON, typeof(Weapon)},
{Item.ItemType.NONE, typeof(Item)}
};
public override bool CanWrite => false;
public override bool CanConvert(Type objectType) => typeof(Item).IsAssignableFrom(objectType);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
// Load JObject from stream
var jObject = JObject.Load(reader);
// Create target object based on JObject
Type targetObjectType = objectType;
if (jObject["itemType"] != null)
{
targetObjectType = this.map.TryGetValue((Item.ItemType)(int)(jObject["itemType"]), out targetObjectType) ? targetObjectType : objectType;
// return new Weapon();
}
object target = Activator.CreateInstance(targetObjectType);
// Populate the object properties
serializer.Populate(jObject.CreateReader(), target);
return target;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Usage:
Dictionary<string, Item> items = new Dictionary<string, Item>();
items = JsonConvert.DeserializeObject<Dictionary<string, Item>>(json, new JsonSerializerSettings
{
Converters = new List<JsonConverter> {new ItemCreationConvertor()}
});
Or decorate Item class with attribute:
[Newtonsoft.Json.JsonConverter(typeof(ItemCreationConvertor))]
public class Item
{
// Other properties go here
}
items = JsonConvert.DeserializeObject<Dictionary<string, Item>>(testJson2);

Related

JsonConvert List to Json Properties

I would want to convert an object with a list property into a json object. The list property should not be parsed directly, instead the items in the list should be added to the json object as properties. The property names should be custom and have the index as suffix. Only the serialzation is needed.
Example:
class Foo{
public bool Boolean { get; set; }
public List<Bar> List { get; set; }
// 50+ additional properties to be serialized.
}
class Bar{
public string Value{ get; set; }
}
Code:
var bar = new Foo();
bar.Boolean = true;
bar.List = new List<Bar>() {
new Bar(){ Value = "Foo1" },
new Bar(){ Value = "Bar2" },
new Bar(){ Value = "Foo3" }
};
JsonConvert.Serialize(bar)
Desired output:
{
"Boolean": true,
"property1" : "Foo1",
"property2" : "Bar2",
"property3" : "Foo3"
}
Note that, in my real classes, there are over 50 other properties for Foo so practically I cannot write a JsonConverter<Foo> that serializes each one manually.
Here is one other approach to produce expected JSON without JSON converter
Redefine the POCO object as below
public class Foo
{
public bool Boolean;
[JsonIgnore]
public List<Bar> List;
[JsonExtensionData]
public Dictionary<string, JToken> Values
{
get
{
return List.Select((v, i) => new KeyValuePair<string, JToken>($"Property{i + 1}", JValue.CreateString(v.Value))).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
}
}
}
public class Bar{
public string Value{ get; set; }
}
Then use JSON library to serialize as below
var bar = new Foo();
bar.Boolean = true;
bar.List = new List<Bar>() {
new Bar(){ Value = "Foo1" },
new Bar(){ Value = "Bar2" },
new Bar(){ Value = "Foo3" }
};
var json = JsonConvert.SerializeObject(bar, Formatting.Indented);
Console.WriteLine(json);
Sample fiddle: https://dotnetfiddle.net/jgWlLB
You can generate the JSON you require with a custom JsonConverter. Since your "real" data model has 50+ properties, you can use Json.NET's own contract metadata to loop through the properties of Foo and serialize them appropriately. The following does the trick:
public class ItemListContainerConverter<TContainer, TItem> : JsonConverter<TContainer>
{
protected virtual string PropertyName => "property";
public override void WriteJson(JsonWriter writer, TContainer value, JsonSerializer serializer)
{
var contract = serializer.ContractResolver.ResolveContract(value.GetType()) as JsonObjectContract;
if (contract == null)
throw new JsonSerializationException("value is not a JSON object");
JsonProperty itemListProperty = null;
writer.WriteStartObject();
foreach (var property in contract.Properties.Where(p => ShouldSerialize(p, value)))
{
var propertyValue = property.ValueProvider.GetValue(value);
if (propertyValue == null && (serializer.NullValueHandling == NullValueHandling.Ignore || property.NullValueHandling == NullValueHandling.Ignore))
continue;
if (typeof(IEnumerable<TItem>).IsAssignableFrom(property.PropertyType))
{
itemListProperty = (itemListProperty == null ? property : throw new JsonSerializationException("Too many IEnumerable<Bar> properties"));
}
else
{
writer.WritePropertyName(property.PropertyName);
if (propertyValue == null)
writer.WriteNull();
else if (property.Converter != null && property.Converter.CanWrite)
property.Converter.WriteJson(writer, propertyValue, serializer);
else
serializer.Serialize(writer, propertyValue);
}
}
if (itemListProperty != null)
{
var itemList = itemListProperty.ValueProvider.GetValue(value) as IEnumerable<TItem>;
if (itemList != null)
{
var itemContract = serializer.ContractResolver.ResolveContract(typeof(TItem));
var valueMethod = GetItemValueMethod(itemContract);
int i = 1;
foreach (var item in itemList)
{
writer.WritePropertyName(PropertyName + (i++).ToString(CultureInfo.InvariantCulture));
serializer.Serialize(writer, valueMethod(item));
}
}
}
writer.WriteEndObject();
}
protected virtual Func<object, object> GetItemValueMethod(JsonContract itemContract)
{
if (!(itemContract is JsonObjectContract objectContract))
throw new JsonSerializationException("item contract is not a JsonObjectContract");
var property = objectContract.Properties.Single(p => ShouldSerialize(p));
return (o) => property.ValueProvider.GetValue(o);
}
protected virtual bool ShouldSerialize(JsonProperty property) => property.Readable && !property.Ignored;
protected virtual bool ShouldSerialize(JsonProperty property, object value) => ShouldSerialize(property) && (property.ShouldSerialize == null || property.ShouldSerialize(value));
public override TContainer ReadJson(JsonReader reader, Type objectType, TContainer existingValue, bool hasExistingValue, JsonSerializer serializer) =>
// Not requested to be implemnented as per the question.
throw new NotImplementedException();
}
And then you can serialize your model Foo as follows:
var settings = new JsonSerializerSettings
{
Converters = { new ItemListContainerConverter<Foo, Bar>() }
};
var json = JsonConvert.SerializeObject(foo, Formatting.Indented, settings);
Which results in
{
"Boolean": true,
"property1": "Foo1",
"property2": "Bar2",
"property3": "Foo3"
}
Notes:
This solution does not require serialization to an intermediate JToken representation.
The converter ItemListContainerConverter<TContainer, TItem> assumes that the container type TContainer will be serialized as a JSON object that has a single property of type IEnumerable<TItem> and any number of additional properties. The additional properties will be serialized normally while the items of the IEnumerable<TItem> collection will be serialized as "propertyI" properties as required.
It assumes that the item type TItem has a single serializable property. If your TItem has more than one property, you can override GetItemValueMethod() to select the property you wish to serialize as the value of the "propertyI" properties.
Deserialization was not implemented as it was not requested in the question.
Demo fiddle here.
Classes:
public class Foo
{
public bool Boolean;
[JsonIgnore]
public List<Bar> List;
}
class Bar{
string Value{ get; set; }
}
public class FooConverter : JsonConverter<Foo>
{
public override Foo ReadJson(JsonReader reader, Type objectType, Foo existingValue, bool hasExistingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, Foo value, JsonSerializer serializer)
{
var t = JToken.FromObject(value);
JObject o = (JObject)t;
for (int i = 0; i < value.List.Count; i++)
{
o.Add($"prop{i + 1}", new JValue(value.List[i].Value));
}
o.WriteTo(writer);
}
}
Code:
var bar = new Foo();
bar.Boolean = true;
bar.List = new List<Bar>() {
new Bar(){ Value = "Foo1" },
new Bar(){ Value = "Bar2" },
new Bar(){ Value = "Foo3" }
};
JsonConvert.SerializeObject(bar, Formatting.Indented, new FooConverter())
My earlier attempts failed because i tried the simple approach of just passing bar to SerialzeObjects and adding the JsonConverter attribute to the Foo class and so it kept looping on itself.
try this
JObject o = (JObject)JToken.FromObject(bar);
var i = 0;
foreach (var item in (JArray)o["List"])
{
i++;
o.Add("property" + i.ToString(), item["Value"]);
}
o.Remove("List");
o.ToString();
result
{
"Boolean": true,
"property1": "Foo1",
"property2": "Bar2",
"property3": "Foo3"
}

Json.Net: How to ignore null elements in array deserializing a JSON

I have this JSON:
{
"Variable1": "1",
"Variable2": "50000",
"ArrayObject": [null]
}
I have this stubs:
public class Class1
{
public string Variable1 { get; set; }
public string Variable2 { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public List<ArrayObject> ArrayObject { get; set; }
}
public class ArrayObject
{
public string VariableArray1 { get; set; }
public string VariableArray2 { get; set; }
}
I'd like to ignore the null elements inside array preferably using the json settings or some sort of converter. So the result should be an empty array in that case or null.
Here is the code I've been trying to make this work.
class Program
{
static void Main(string[] args)
{
string json = #"{
""Variable1"": ""1"",
""Variable2"": ""50000"",
""ArrayObject"": [null]
}";
var settings = new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore,
};
Class1 class1 = JsonConvert.DeserializeObject<Class1>(json, settings);
Console.WriteLine(class1.ArrayObject == null);
Console.WriteLine(class1.ArrayObject.Count());
foreach (var item in class1.ArrayObject)
{
Console.WriteLine(item.VariableArray1);
Console.WriteLine(item.VariableArray2);
Console.WriteLine("#######################");
}
}
public class Class1
{
public string Variable1 { get; set; }
public string Variable2 { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public List<ArrayObject> ArrayObject { get; set; }
}
public class ArrayObject
{
public string VariableArray1 { get; set; }
public string VariableArray2 { get; set; }
}
}
I thought that using NullValueHandling = NullValueHandling.Ignore would make it work. Apparently not. Any ideas?
Update: I need a global solution, I don't want to have to modify every viewmodel inside my project.
Setting NullValueHandling = NullValueHandling.Ignore will not filter null values from JSON arrays automatically during deserialization because doing so would cause the remaining items in the array to be re-indexed, rendering invalid any array indices that might have been stored elsewhere in the serialization graph.
If you don't care about preserving array indices and want to filter null values from the array during deserialization anyway, you will need to implement a custom JsonConverter such as the following:
public class NullFilteringListConverter<T> : JsonConverter<List<T>>
{
public override List<T> ReadJson(JsonReader reader, Type objectType, List<T> existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
return null;
var list = existingValue as List<T> ?? (List<T>)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
serializer.Populate(reader, list);
list.RemoveAll(i => i == null);
return list;
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, List<T> value, JsonSerializer serializer) => throw new NotImplementedException();
}
public static partial class JsonExtensions
{
public static JsonReader MoveToContentAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (reader.TokenType == JsonToken.None) // Skip past beginning of stream.
reader.ReadAndAssert();
while (reader.TokenType == JsonToken.Comment) // Skip past comments.
reader.ReadAndAssert();
return reader;
}
public static JsonReader ReadAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (!reader.Read())
throw new JsonReaderException("Unexpected end of JSON stream.");
return reader;
}
}
And apply it to your model as follows:
public class Class1
{
public string Variable1 { get; set; }
public string Variable2 { get; set; }
[JsonConverter(typeof(NullFilteringListConverter<ArrayObject>))]
public List<ArrayObject> ArrayObject { get; set; }
}
Or, add it in settings as follows:
var settings = new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore,
Converters = { new NullFilteringListConverter<ArrayObject>() },
};
Notes:
Since you didn't ask about filtering null values during serialization, I didn't implement it, however it would be easy to do by changing CanWrite => true; and replacing WriteJson() with:
public override void WriteJson(JsonWriter writer, List<T> value, JsonSerializer serializer) => serializer.Serialize(writer, value.Where(i => i != null));
Demo fiddles here and here.
Update
I need a global solution. If you need to automatically filter all null values from all possible List<T> objects in every model, the following JsonConverter will do the job:
public class NullFilteringListConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
if (objectType.IsArray || objectType == typeof(string) || objectType.IsPrimitive)
return false;
var itemType = objectType.GetListItemType();
return itemType != null && (!itemType.IsValueType || Nullable.GetUnderlyingType(itemType) != null);
}
object ReadJsonGeneric<T>(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var list = existingValue as List<T> ?? (List<T>)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
serializer.Populate(reader, list);
list.RemoveAll(i => i == null);
return list;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
return null;
var itemType = objectType.GetListItemType();
var method = typeof(NullFilteringListConverter).GetMethod("ReadJsonGeneric", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
try
{
return method.MakeGenericMethod(new[] { itemType }).Invoke(this, new object[] { reader, objectType, existingValue, serializer });
}
catch (Exception ex)
{
// Wrap the TargetInvocationException in a JsonSerializerException
throw new JsonSerializationException("Failed to deserialize " + objectType, ex);
}
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
}
public static partial class JsonExtensions
{
internal static Type GetListItemType(this Type type)
{
// Quick reject for performance
if (type.IsPrimitive || type.IsArray || type == typeof(string))
return null;
while (type != null)
{
if (type.IsGenericType)
{
var genType = type.GetGenericTypeDefinition();
if (genType == typeof(List<>))
return type.GetGenericArguments()[0];
}
type = type.BaseType;
}
return null;
}
}
And add it to settings as follows:
var settings = new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore,
Converters = { new NullFilteringListConverter() },
};
Class1 class1 = JsonConvert.DeserializeObject<Class1>(json, settings);
With this converter, adding [JsonConverter(typeof(NullFilteringListConverter<ArrayObject>))] to ArrayObject is no longer required. Do note that all List<T> instances in your deserialization graph may get re-indexed whenever these settings are used! Make sure you really want this as the side-effects of changing indices of items referred to elsewhere by index may include data corruption (incorrect references) rather than an outright ArgumentOutOfRangeException.
Demo fiddle #3 here.
You could also have a custom setter that filters out null values.
private List<ArrayObject> _arrayObject;
public List<ArrayObject> ArrayObject
{
get => _arrayObject;
set
{
_arrayObject = value.Where(x => x != null).ToList();
}
}
Fiddle working here https://dotnetfiddle.net/ePp0A2
NullValueHandling.Ignore does not filter null values from an array. But you can do this easily by adding a deserialization callback method in your class to filter out the nulls:
public class Class1
{
public string Variable1 { get; set; }
public string Variable2 { get; set; }
public List<ArrayObject> ArrayObject { get; set; }
[OnDeserialized]
internal void OnDeserialized(StreamingContext context)
{
ArrayObject?.RemoveAll(o => o == null);
}
}
Working demo: https://dotnetfiddle.net/v9yn7j

How can I flatten a Dictionary member into the containing object when serializing to Json? [duplicate]

I'm using Json.Net for serialization.
I have a class with a Dictionary:
public class Test
{
public string X { get; set; }
public Dictionary<string, string> Y { get; set; }
}
Can I somehow serialize this object to get the following JSON
{
"X" : "value",
"key1": "value1",
"key2": "value2"
}
where "key1", "key2" are keys in the Dictionary?
If you're using Json.Net 5.0.5 or later and you're willing to change the type of your dictionary from Dictionary<string, string> to Dictionary<string, object>, then one easy way to accomplish what you want is to add the [JsonExtensionData] attribute to your dictionary property like this:
public class Test
{
public string X { get; set; }
[JsonExtensionData]
public Dictionary<string, object> Y { get; set; }
}
The keys and values of the marked dictionary will then be serialized as part of the parent object. The bonus is that it works on deserialization as well: any properties in the JSON that do not match to members of the class will be placed into the dictionary.
Implement JsonConverter-derived class: the CustomCreationConverter class should be used as base class to create a custom object.
Draft version of the converter (error handling can be improved as you wish):
internal class TestObjectConverter : CustomCreationConverter<Test>
{
#region Overrides of CustomCreationConverter<Test>
public override Test Create(Type objectType)
{
return new Test
{
Y = new Dictionary<string, string>()
};
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteStartObject();
// Write properties.
var propertyInfos = value.GetType().GetProperties();
foreach (var propertyInfo in propertyInfos)
{
// Skip the Y property.
if (propertyInfo.Name == "Y")
continue;
writer.WritePropertyName(propertyInfo.Name);
var propertyValue = propertyInfo.GetValue(value);
serializer.Serialize(writer, propertyValue);
}
// Write dictionary key-value pairs.
var test = (Test)value;
foreach (var kvp in test.Y)
{
writer.WritePropertyName(kvp.Key);
serializer.Serialize(writer, kvp.Value);
}
writer.WriteEndObject();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jsonObject = JObject.Load(reader);
var jsonProperties = jsonObject.Properties().ToList();
var outputObject = Create(objectType);
// Property name => property info dictionary (for fast lookup).
var propertyNames = objectType.GetProperties().ToDictionary(pi => pi.Name, pi => pi);
foreach (var jsonProperty in jsonProperties)
{
// If such property exists - use it.
PropertyInfo targetProperty;
if (propertyNames.TryGetValue(jsonProperty.Name, out targetProperty))
{
var propertyValue = jsonProperty.Value.ToObject(targetProperty.PropertyType);
targetProperty.SetValue(outputObject, propertyValue, null);
}
else
{
// Otherwise - use the dictionary.
outputObject.Y.Add(jsonProperty.Name, jsonProperty.Value.ToObject<string>());
}
}
return outputObject;
}
public override bool CanWrite
{
get { return true; }
}
#endregion
}
Client code:
var test = new Test
{
X = "123",
Y = new Dictionary<string, string>
{
{ "key1", "value1" },
{ "key2", "value2" },
{ "key3", "value3" },
}
};
string json = JsonConvert.SerializeObject(test, Formatting.Indented, new TestObjectConverter());
var deserializedObject = JsonConvert.DeserializeObject<Test>(json);
Please note: there is a potential collision between property names and key names of the dictionary.
You can create this converter and then assign it to your property. Took bits and pieces of of proposed solutions.
public class DictionaryToJsonObjectConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(IDictionary<string, string>).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteRawValue(JsonConvert.SerializeObject(value, Formatting.Indented));
}
}
Then use it in your poco class.
public class Poco
{
[JsonProperty("myid")]
public string Id{ get; set; }
[JsonProperty("properties")]
[JsonConverter(typeof(DictionaryToJsonObjectConverter))]
public IDictionary<string, string> Properties { get; set; }
}

How to Serialize and Deserialize ArrayList of Arrays by using Json.NET

I need to serialize/deserialize an object to json by using JSON.NET in my application. Object has a property type of ArrayList which contains string arrays. To simulate it I've written the following unit test;
public class JsonTests
{
public class MyTestClass
{
public ArrayList Items { get; private set; }
public MyTestClass()
{
this.Items = new ArrayList();
}
}
[Fact]
public void JsonConvert_Should_Serialize_ArrayList_And_Deserialize_Successfully()
{
MyTestClass myObject = new MyTestClass();
myObject.Items.Add(new string[] { "Test1", "Test2" });
string jsonResult = JsonConvert.SerializeObject(myObject);
MyTestClass tempMyObject = JsonConvert.DeserializeObject<MyTestClass>(jsonResult);
//(tempMyObject.Items[0] as string[]) casts to null therefore it throws exception
Assert.Equal((myObject.Items[0] as string[])[0], (tempMyObject.Items[0] as string[])[0]);
}
}
It does not throw any exception durion serialization. However, it does not deserialize ArrayList properly.
My question is , how can I deserialize it back to ArrayList of string arrays ?
Update: In addition, I cannot change the class definition. The class is implemented in an assembly where I cannot edit the class.
ArrayList is a non-generic, untyped collection so you need to inform Json.NET of the expected type of the items. One way to do this is with a custom JsonConverter for the ArrayList:
public class ArrayListConverter<TItem> : JsonConverter
{
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var list = serializer.Deserialize<List<TItem>>(reader);
var arrayList = existingValue as ArrayList ?? new ArrayList(list.Count);
arrayList.AddRange(list);
return arrayList;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ArrayList);
}
}
Then apply the converter to the class as follows:
public class MyTestClass
{
[JsonConverter(typeof(ArrayListConverter<string []>))]
public ArrayList Items { get; private set; }
public MyTestClass()
{
this.Items = new ArrayList();
}
}
Sample fiddle.
If the class cannot be modified, and you want all instances of ArrayList in your object graph to deserialize their items as string [], you can add the converter to JsonSerializerSettings.Converters instead of adding it to the type:
var tempMyObject = JsonConvert.DeserializeObject<MyTestClass>(jsonResult,
new JsonSerializerSettings { Converters = { new ArrayListConverter<string []>() } });
Sample fiddle #2.
And finally, if the class cannot be modified and you only want the specific ArrayList Items property inside MyTestClass to have its items deserialized as string [], you will need to create a custom converter for MyTestClass. You can use the pattern from Custom deserializer only for some fields with json.NET to custom-deserialize the ArrayList property while populating the remainder with default deserialization:
public class MyTestClassConverter : JsonConverter
{
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var root = existingValue as MyTestClass ?? (MyTestClass)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
var jsonObject = JObject.Load(reader);
var jsonItems = jsonObject["Items"].RemoveFromLowestPossibleParent();
if (jsonItems != null && jsonItems.Type != JTokenType.Null)
{
root.Items.AddRange(jsonItems.ToObject<List<string []>>());
}
// Populate the remaining standard properties
using (var subReader = jsonObject.CreateReader())
{
serializer.Populate(subReader, root);
}
return root;
}
public override bool CanConvert(Type objectType)
{
return typeof(MyTestClass).IsAssignableFrom(objectType);
}
}
public static class JsonExtensions
{
public static JToken RemoveFromLowestPossibleParent(this JToken node)
{
if (node == null)
return null;
var contained = node.AncestorsAndSelf().Where(t => t.Parent is JContainer && t.Parent.Type != JTokenType.Property).FirstOrDefault();
if (contained != null)
contained.Remove();
// Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
if (node.Parent is JProperty)
((JProperty)node.Parent).Value = null;
return node;
}
}
Then use it as follows:
var tempMyObject = JsonConvert.DeserializeObject<MyTestClass>(jsonResult,
new JsonSerializerSettings { Converters = { new MyTestClassConverter() } });
Sample fiddle #3.
Try this I hope this code help you
using List and JSON.NET
List<string[]> list = new List<string[]>();
list.Add(new string[] { "Value","value" });
list.Add(new string[] { "b2", "b22" });
var ee = JsonConvert.SerializeObject(list);
Console.WriteLine(ee);
List<string[]> ll = JsonConvert.DeserializeObject<List<string[]>>(ee);
foreach (var Valus in ll)
{
foreach (var val in Valus)
{
Console.WriteLine(val);
}
}
using Array List
string[][] strarry = { new string[] { "f1", "f2", "f3" }, new string[] { "s1", "s2", "s3" }, new string[] { "t1", "t2", "t3" } };
string SerializeArray = json.Serialize(strarry);
string[][] DeSerializeArrayList = json.Deserialize<string[][]>(SerializeArray);
foreach (var item in DeSerializeArrayList)
{
foreach (var Subitem in item)
{
Response.Write(Subitem + "<br/>");
}
}
using List
List<Data> list = new List<Data>();
list.Add(new Data() { ID = 1, Name = "val1" });
list.Add(new Data() { ID = 2, Name = "val2" });
JavaScriptSerializer json = new JavaScriptSerializer();
string Serialize = json.Serialize(list);
Response.Write(Serialize);
List<Data> DeSerialize = json.Deserialize<List<Data>>(Serialize);
foreach (var Data in DeSerialize)
{
Response.Write(Data.Name);
}
Data Class
public class Data
{
public int ID { get; set; }
public string Name { get; set; }
}

How to deserialize a complex separated ("Column"/"Data") json to c# object using json.net

I need to parse the following JSON into a List of PackageEntity Object.
Since this json is divided into Column and Data, I am having trouble to do so in an intelligent way.
The JSON looks like:
{
"COLUMNS": ["NSHIPMENTID", "NSHIPPINGCOMPANYID", "NUSERID", "NWEIGHT", "NHEIGHT"],
"DATA": [
[7474, null, 12363, "16", "2"],
[7593, null, 12363, "64", "7"]
]
}
I would like to deserialize it to a list of the following class:
public class PackageEntity
{
public int NSHIPMENTID { get; set; }
public string NSHIPPINGCOMPANYID { get; set; }
public int NUSERID { get; set; }
public decimal NWEIGHT { get; set; }
public decimal NHEIGHT { get; set; }
}
what i did so far:
JObject JsonDe = JObject.Parse(responseString);
int length = JsonDe.Property("DATA").Value.ToArray().Count();
List<PackageEntity> _list = new List<PackageEntity>();
for (int i = 0; i < length; i++)
{
PackageEntity pD = new PackageEntity();
pD.NSHIPMENTID = JsonDe.Property("DATA").Value.ToArray()[i][0].ToString();
pD.NSHIPPINGCOMPANYID = JsonDe.Property("DATA").Value.ToArray()[i][1].ToString();
pD.NUSERID = JsonDe.Property("DATA").Value.ToArray()[i][2].ToString();
pD.NWEIGHT = JsonDe.Property("DATA").Value.ToArray()[i][3].ToString();
pD.NHEIGHT = JsonDe.Property("DATA").Value.ToArray()[i][4].ToString();
_list.Add(pD);
}
You can use the following generic custom JsonConverter to deserialize your data:
public class ColumnarDataToListConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(List<T>);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var list = existingValue as List<T> ?? new List<T>();
var obj = JObject.Load(reader);
var columns = obj["COLUMNS"] as JArray;
var data = obj["DATA"] as JArray;
if (data == null)
return list;
list.AddRange(data
.Select(item => new JObject(columns.Zip(item, (c, v) => new JProperty((string)c, v))))
.Select(o => o.ToObject<T>(serializer)));
return list;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then use it as follows:
var settings = new JsonSerializerSettings { Converters = new[] { new ColumnarDataToListConverter<PackageEntity>() } };
var list = JsonConvert.DeserializeObject<List<PackageEntity>>(responseString, settings);
Note the use of Enumerable.Zip() to pair up the entries in the column array with the entries in each row of the data array into a temporary JObject for deserialization.

Categories

Resources