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"
}
Related
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);
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; }
}
I'm trying to create a custom JsonConverter that changes C# property names to camel case and javascript/json property names to pascal case. I feel like I'm on the right track but I'm having trouble understanding what I need to do (and I'm in a time crunch).
I realize I can add the JsonProperty attribute to my C# properties but I would prefer to apply an attribute to the class rather than each property.
public class ViewModelJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var model = JObject.Load(reader);
var properties = model.Properties();
foreach (var prop in properties)
{
RenameToPascalCase(prop.Name, prop.Value);
}
return model;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var model = (JObject)JToken.FromObject(value);
var properties = model.Properties();
foreach (var prop in properties)
{
RenameToCamelCase(prop.Name, prop.Value);
}
}
private void RenameToCamelCase(string name, JToken value)
{
var parent = value.Parent;
if (parent == null)
throw new InvalidOperationException("The parent is missing.");
var newProperty = new JProperty(ToCamelCase(name), value);
parent.Replace(newProperty);
}
private void RenameToPascalCase(string name, JToken value)
{
var parent = value.Parent;
if (parent == null)
throw new InvalidOperationException("The parent is missing.");
var newProperty = new JProperty(ToPascalCase(name), value);
parent.Replace(newProperty);
}
//Example: propertyName
private string ToCamelCase(string value)
{
if (String.IsNullOrEmpty(value) || Char.IsLower(value, 0))
return value;
return Char.ToLowerInvariant(value[0]) + value.Substring(1);
}
//Example: PropertyName
private string ToPascalCase(string value)
{
if (String.IsNullOrEmpty(value) || Char.IsUpper(value, 0))
return value;
return Char.ToUpperInvariant(value[0]) + value.Substring(1);
}
}
Sample Use
[JsonConverter(typeof(ViewModelJsonConverter))]
public class TestClass {
public string PropertyName { get; set; }
}
If you're using Json.Net 9.0.1 or later, you can do what you want by using the NamingStrategyType parameter of the [JsonObject] attribute. So, in other words, just mark the classes you want to be camel cased like this:
[JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
public class TestClass
{
...
}
You don't need a ContractResolver or a custom JsonConverter.
Here is a round-trip demo:
public class Program
{
public static void Main(string[] args)
{
TestClass tc = new TestClass
{
PropertyName = "foo",
AnotherPropertyName = "bar"
};
Console.WriteLine("--- Serialize ---");
string json = JsonConvert.SerializeObject(tc, Formatting.Indented);
Console.WriteLine(json);
Console.WriteLine();
Console.WriteLine("--- Deserialize ---");
TestClass test = JsonConvert.DeserializeObject<TestClass>(json);
Console.WriteLine("PropertyName: " + test.PropertyName);
Console.WriteLine("AnotherPropertyName: " + test.AnotherPropertyName);
}
}
[JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
public class TestClass
{
public string PropertyName { get; set; }
public string AnotherPropertyName { get; set; }
}
Output:
--- Serialize ---
{
"propertyName": "foo",
"anotherPropertyName": "bar"
}
--- Deserialize ---
PropertyName: foo
AnotherPropertyName: bar
Have you ever tried any of these contract resolvers for newtonsoft json.
For eg:
var jsonSerializerSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() };
var json = JsonConvert.SerializeObject(obj, Formatting.Indented, jsonSerializerSettings);
If you wanted to implement your own then please disregard.
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.
I want to turn a flat json string into a model, the destination class has subclasses, and the flat json has all of the sub class objects with prefix; like "{classname}.{property}".
{
"FirstName": "Joey",
"LastName": "Billy",
"EmploymentDetails.JobTitle": "JobTitle",
"EmploymentDetails.StartDate": "2015-01-01T00:00:00",
"ContactDetails.HouseNumberName": "10",
"ContactDetails.Road": "Road"
}
This is my destination class:
public class Person {
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual EmploymentDetails EmploymentDetails { get;set;}
public virtual ContactDetails ContactDetails { get;set;}
}
public class EmploymentDetails {
public string JobTitle { get; set; }
public DateTime StartDate { get; set; }
}
public class ContactDetails {
public string HouseNumberName { get; set; }
public string Road { get; set; }
}
I tried the following:
public static void main() {
var json = #"{""FirstName"": ""Joey"",""LastName"": ""Billy"",""EmploymentDetails.JobTitle"": ""JobTitle"",""EmploymentDetails.StartDate"": ""2015-01-01T00:00:00"",""ContactDetails.HouseNumberName"": ""10"",""ContactDetails.Road"": ""Road"",}";
//try using AutoMapper
Mapper.CreateMap<string,Person>();
var personModel = Mapper.Map<Person>(json);
//just returns null values
//try using Newtonsoft
personModel = Newtonsoft.Json.JsonConvert.DeserializeObject<Person>(json);
//fills values but obviously returns no nested data
}
I know that Automapper has RecognizePrefix and RecognizeDestinationPrefix, but AutoMapper seems to only care if it's in the orignal object, and not a sub class.
Potentially I could take my JSON string and make it a Dictionary, but even then I don't know how to map it to a model with sub classes.
There was hope that I could have an infinite amount of sub classes and the JSON string could just map the flat JSON model to a model.
You can make a JsonConverter that does this in a generic way, using a ContractResolver to group and populate properties in the class being deserialized or its contained classes as appropriate.
You didn't ask for serialization, only deserialization, so that's what this does:
public class JsonFlatteningConverter : JsonConverter
{
readonly IContractResolver resolver;
public JsonFlatteningConverter(IContractResolver resolver)
{
if (resolver == null)
throw new ArgumentNullException();
this.resolver = resolver;
}
public override bool CanConvert(Type objectType)
{
return resolver.ResolveContract(objectType) is JsonObjectContract;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
JObject jObject = JObject.Load(reader);
var contract = (JsonObjectContract)resolver.ResolveContract(objectType); // Throw an InvalidCastException if this object does not map to a JObject.
existingValue = existingValue ?? contract.DefaultCreator();
if (jObject.Count == 0)
return existingValue;
var groups = jObject.Properties().GroupBy(p => p.Name.Contains('.') ? p.Name.Split('.').FirstOrDefault() : null).ToArray();
foreach (var group in groups)
{
if (string.IsNullOrEmpty(group.Key))
{
var subObj = new JObject(group);
using (var subReader = subObj.CreateReader())
serializer.Populate(subReader, existingValue);
}
else
{
var jsonProperty = contract.Properties[group.Key];
if (jsonProperty == null || !jsonProperty.Writable)
continue;
if (jsonProperty != null)
{
var subObj = new JObject(group.Select(p => new JProperty(p.Name.Substring(group.Key.Length + 1), p.Value)));
using (var subReader = subObj.CreateReader())
{
var propertyValue = serializer.Deserialize(subReader, jsonProperty.PropertyType);
jsonProperty.ValueProvider.SetValue(existingValue, propertyValue);
}
}
}
}
return existingValue;
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
And then use it thusly:
var resolver = new DefaultContractResolver();
var settings = new JsonSerializerSettings { ContractResolver = resolver, Converters = new JsonConverter[] { new JsonFlatteningConverter(resolver) } };
var person = JsonConvert.DeserializeObject<Person>(json, settings);
Prototype fiddle.