Test1 deserializes msg1 that has a single array (data:[]) successfully. ElementJsonConverter handles the ElementData which is more complicated than in the example.
Test2 tries to deserializes msg2 that has a nested array (data:[[]]). Message2 class has Table class which has List<TableRow> when TableRow is List<ElementData> that I need to populate. I don't understand how to do this. Do I need somehow to have separate converters for Table and TableRow?
Single Array
void Test1()
{
var msg1 = "{\"type\":\"message1\",\"data\":[{\"type\":\"element1\",\"name\":\"myname\",\"amount\":0}]";
var obj = JsonConvert.DeserializeObject<Message1>(msg1);
}
public class Message1
{
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("data", ItemConverterType = typeof(ElementJsonConverter))]
public List<ElementData> Data { get; set; }
}
public abstract class ElementData
{
[JsonProperty("type")]
public string ElementType { get; set; }
[JsonProperty("amount")]
public int Amount { get; set; }
}
public class Element1 : ElementData
{
[JsonProperty("name")]
public string Name{ get; set; }
}
public class Element2 : ElementData
{
[JsonProperty("name")]
public string Name{ get; set; }
//OTHER PROPERTIES
}
Nested Array
void Test2()
{
var msg2 = "{\"type\":\"message2\",\"data\":[[{\"type\":\"element1\",\"name\":\"myname\",\"amount\":0}]]";
var obj = JsonConvert.DeserializeObject<Message1>(msg2);
}
public class Message2
{
[JsonProperty("type")]
public string Type { get; set; }
[JsonConverter(typeof(TableJsonConverter))]
[JsonProperty("data")]
public Table Data { get; set; }
}
public class Table
{
public List<TableRow> Steps { get; set; }
}
public class TableRow
{
[JsonProperty(ItemConverterType = typeof(ElementJsonConverter))]
public List<ElementData> Elements { get; set; }
}
Converters
public class TableJsonConverter : JsonConverter<Table>
{
public override Table ReadJson(JsonReader reader, Type objectType, Table existingValue, bool hasExistingValue, Newtonsoft.Json.JsonSerializer serializer)
{
}
public override void WriteJson(...){}
}
public class ElementJsonConverter : JsonConverter<ElementData>
{
public override ElementData ReadJson(JsonReader reader, Type objectType, ElementData existingValue, bool hasExistingValue, Newtonsoft.Json.JsonSerializer serializer)
{
var jObject = JObject.Load(reader);
var elementType = jObject["type"].Value<string>();
if(elementType == "element1")
return jObject.ToObject<Element1>(serializer);
else if(elementType == "element2")
return jObject.ToObject<Element2>(serializer);
else
throw new Exception($"Unsupported element type [{elementType}]");
}
public override void WriteJson(JsonWriter writer, ElementData value, Newtonsoft.Json.JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
For the provided code (without missing converters) you can just deserialize to collection of collection of elements:
public override Table ReadJson(JsonReader reader, Type objectType, Table existingValue, bool hasExistingValue, Newtonsoft.Json.JsonSerializer serializer)
{
// TODO - handle existing value?
var deserialize = serializer.Deserialize<Element[][]>(reader); // or ElementData if converter handles it
return new Table
{
Steps = deserialize.Select(x => new TableRow
{
Elements = x.ToList<ElementData>()
})
.ToList()
};
return default;
}
For more dynamic approach you can use var jArr = JArray.Load(reader) and process it.
UPD
Changed var deserialize = serializer.Deserialize<Element[][]>(reader); to var deserialize = serializer.Deserialize<ElementData[][]>(reader); with following changes to the ElementData class:
[JsonConverter(typeof(ElementJsonConverter))]
public abstract class ElementData
{
[JsonProperty("type")]
public string ElementType { get; set; }
[JsonProperty("amount")]
public int Amount { get; set; }
}
And converter:
public class ElementJsonConverter : JsonConverter
{
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue,
JsonSerializer serializer)
{
var jObject = JObject.Load(reader);
var elementType = jObject["type"].Value<string>();
ElementData value;
if (elementType == "element1")
value = new Element1();
else if (elementType == "element2")
value = new Element2();
else
throw new Exception($"Unsupported element type [{elementType}]");
serializer.Populate(jObject.CreateReader(), value);
return value;
}
public override bool CanConvert(Type objectType) => typeof(ElementData) == objectType;
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) =>
throw new NotImplementedException();
}
IMHO I'd rather to use one root class, if you want more, it is better to create a separate converter for each class
public class Message
{
public string Type { get; set; }
public List<List<ElementData>> Data { get; set; }
[JsonConstructor]
public Message(string type,JToken data)
{
Type = type;
if(type == "message2")
Data = data.Select(x => x.Select(y => (ElementData)y.ToObject<Element>()).ToList()).ToList();
else Data = new List<List<ElementData>> { data.Select(x => (ElementData)x.ToObject<Element>()).ToList() };
}
}
or if you like converters
public class MessageJsonConverter : JsonConverter<Message>
{
public override Message ReadJson(JsonReader reader, Type objectType, Message existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var data = (JObject)serializer.Deserialize(reader);
return new Message
{
Type = data["type"].ToString(),
Data = data["type"].ToString()=="message2"
? data["data"].Select(x => x.Select(y => (ElementData)y.ToObject<Element>()).ToList()).ToList()
: new List<List<ElementData>> { data["data"].Select(x => (ElementData)x.ToObject<Element>()).ToList() }
};
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, Message value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Related
I want deserialize JSON into an object in C#
But one of the properties can be
List<double> or List<List<double>> or List<List<List<double>>>>
And it depends on another property
How can this be done?
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("coordinates")]
public List<double> Coordinates { get; set; }
How can I add that if the Type = X -> Coordinates = List<double>
And if Type = Y -> Coordinates = List<List<double>> and so on
I was able to compile quick piece of code based on your question. So you know, I used Newtonsoft to write it as I already had a sample available that I needed to modify a little for your usage.
//Classes
class TypeBase
{
[JsonProperty("type")]
public string Type { get; set; }
}
class TypeX : TypeBase
{
[JsonProperty("coordinates")]
public List<double> Coordinates { get; set; }
}
class TypeY : TypeBase
{
[JsonProperty("coordinates")]
public List<List<double>> Coordinates { get; set; }
}
Below is the Custom JsonConverter that you'll need
public class DataObjectConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(TypeX) || objectType == typeof(TypeY) || objectType == typeof(TypeBase);
}
public override bool CanRead => true;
public override bool CanWrite => false;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var obj = JObject.Load(reader);
var dataType = obj.Property("type", StringComparison.InvariantCultureIgnoreCase);
if ("X".Equals(dataType?.Value.ToString(), StringComparison.InvariantCultureIgnoreCase))
{
return obj?.ToObject<TypeX>();
}
if ("Y".Equals(dataType?.Value.ToString(), StringComparison.InvariantCultureIgnoreCase))
{
return obj?.ToObject<TypeY>();
}
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Finally, here is the code on how to use the whole thing:
var serializerSettings = new JsonSerializerSettings();
serializerSettings.Converters.Add(new DataObjectConverter());
var typeX = JsonConvert.DeserializeObject<TypeBase>(t, serializerSettings);
var typeY = JsonConvert.DeserializeObject<TypeBase>(ty, serializerSettings);
if (typeX.Type == "X")
{
var typeXObj = (TypeX)typeX;
Console.WriteLine(typeXObj.Coordinates.Count);
}
//etc.
I am trying to deserialize a JSON string to a concrete class, which inherits from an abstract class, but I just can't get it working. I have googled and tried some solutions but they don't seem to work either.
This is what I have now:
abstract class AbstractClass { }
class ConcreteClass { }
public AbstractClass Decode(string jsonString)
{
JsonSerializerSettings jss = new JsonSerializerSettings();
jss.TypeNameHandling = TypeNameHandling.All;
return (AbstractClass)JsonConvert.DeserializeObject(jsonString, null, jss);
}
However, if I try to cast the resulting object, it just doesn't work.
The reason why I don't use DeserializeObject is that I have many concrete classes.
Any suggestions?
I am using Newtonsoft.Json
One may not want to use TypeNameHandling (because one wants more compact json or wants to use a specific name for the type variable other than "$type"). Meanwhile, the customCreationConverter approach will not work if one wants to deserialize the base class into any of multiple derived classes without knowing which one to use in advance.
An alternative is to use an int or other type in the base class and define a JsonConverter.
[JsonConverter(typeof(BaseConverter))]
abstract class Base
{
public int ObjType { get; set; }
public int Id { get; set; }
}
class DerivedType1 : Base
{
public string Foo { get; set; }
}
class DerivedType2 : Base
{
public string Bar { get; set; }
}
The JsonConverter for the base class can then deserialize the object based on its type. The complication is that to avoid a stack overflow (where the JsonConverter repeatedly calls itself), a custom contract resolver must be used during this deserialization.
public class BaseSpecifiedConcreteClassConverter : DefaultContractResolver
{
protected override JsonConverter ResolveContractConverter(Type objectType)
{
if (typeof(Base).IsAssignableFrom(objectType) && !objectType.IsAbstract)
return null; // pretend TableSortRuleConvert is not specified (thus avoiding a stack overflow)
return base.ResolveContractConverter(objectType);
}
}
public class BaseConverter : JsonConverter
{
static JsonSerializerSettings SpecifiedSubclassConversion = new JsonSerializerSettings() { ContractResolver = new BaseSpecifiedConcreteClassConverter() };
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Base));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
switch (jo["ObjType"].Value<int>())
{
case 1:
return JsonConvert.DeserializeObject<DerivedType1>(jo.ToString(), SpecifiedSubclassConversion);
case 2:
return JsonConvert.DeserializeObject<DerivedType2>(jo.ToString(), SpecifiedSubclassConversion);
default:
throw new Exception();
}
throw new NotImplementedException();
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException(); // won't be called because CanWrite returns false
}
}
That's it. Now you can use serialize/deserialize any derived class. You can also use the base class in other classes and serialize/deserialize those without any additional work:
class Holder
{
public List<Base> Objects { get; set; }
}
string json = #"
[
{
""Objects"" :
[
{ ""ObjType"": 1, ""Id"" : 1, ""Foo"" : ""One"" },
{ ""ObjType"": 1, ""Id"" : 2, ""Foo"" : ""Two"" },
]
},
{
""Objects"" :
[
{ ""ObjType"": 2, ""Id"" : 3, ""Bar"" : ""Three"" },
{ ""ObjType"": 2, ""Id"" : 4, ""Bar"" : ""Four"" },
]
},
]";
List<Holder> list = JsonConvert.DeserializeObject<List<Holder>>(json);
string serializedAgain = JsonConvert.SerializeObject(list);
Debug.WriteLine(serializedAgain);
I would suggest to use CustomCreationConverter in the following way:
public enum ClassDiscriminatorEnum
{
ChildClass1,
ChildClass2
}
public abstract class BaseClass
{
public abstract ClassDiscriminatorEnum Type { get; }
}
public class Child1 : BaseClass
{
public override ClassDiscriminatorEnum Type => ClassDiscriminatorEnum.ChildClass1;
public int ExtraProperty1 { get; set; }
}
public class Child2 : BaseClass
{
public override ClassDiscriminatorEnum Type => ClassDiscriminatorEnum.ChildClass2;
}
public class BaseClassConverter : CustomCreationConverter<BaseClass>
{
private ClassDiscriminatorEnum _currentObjectType;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jobj = JObject.ReadFrom(reader);
_currentObjectType = jobj["Type"].ToObject<ClassDiscriminatorEnum>();
return base.ReadJson(jobj.CreateReader(), objectType, existingValue, serializer);
}
public override BaseClass Create(Type objectType)
{
switch (_currentObjectType)
{
case ClassDiscriminatorEnum.ChildClass1:
return new Child1();
case ClassDiscriminatorEnum.ChildClass2:
return new Child2();
default:
throw new NotImplementedException();
}
}
}
try something like this
public AbstractClass Decode(string jsonString)
{
var jss = new JavaScriptSerializer();
return jss.Deserialize<ConcreteClass>(jsonString);
}
UPDATE
for this scenario methinks all work as you want
public abstract class Base
{
public abstract int GetInt();
}
public class Der:Base
{
int g = 5;
public override int GetInt()
{
return g+2;
}
}
public class Der2 : Base
{
int i = 10;
public override int GetInt()
{
return i+17;
}
}
....
var jset = new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All };
Base b = new Der()
string json = JsonConvert.SerializeObject(b, jset);
....
Base c = (Base)JsonConvert.DeserializeObject(json, jset);
where c type is test.Base {test.Der}
UPDATE
#Gusman suggest use TypeNameHandling.Objects instead of TypeNameHandling.All. It is enough and it will produce a less verbose serialization.
Actually, as it has been stated in an update, the simplest way (in 2019) is to use a simple custom pre-defined JsonSerializerSettings, as explained here
string jsonTypeNameAll = JsonConvert.SerializeObject(priceModels, Formatting.Indented,new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
});
And for deserializing :
TDSPriceModels models = JsonConvert.DeserializeObject<TDSPriceModels>(File.ReadAllText(jsonPath), new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
});
public class CustomConverter : JsonConverter
{
private static readonly JsonSerializer Serializer = new JsonSerializer();
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jObject = JObject.Load(reader);
var typeString = jObject.Value<string>("Kind"); //Kind is a property in json , from which we know type of child classes
var requiredType = RecoverType(typeString);
return Serializer.Deserialize(jObject.CreateReader(), requiredType);
}
private Type RecoverType(string typeString)
{
if (typeString.Equals(type of child class1, StringComparison.OrdinalIgnoreCase))
return typeof(childclass1);
if (typeString.Equals(type of child class2, StringComparison.OrdinalIgnoreCase))
return typeof(childclass2);
throw new ArgumentException("Unrecognized type");
}
public override bool CanConvert(Type objectType)
{
return typeof(Base class).IsAssignableFrom(objectType) || typeof((Base class) == objectType;
}
public override bool CanWrite { get { return false; } }
}
Now add this converter in JsonSerializerSettings as below
var jsonSerializerSettings = new JsonSerializerSettings();
jsonSerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
jsonSerializerSettings.Converters.Add(new CustomConverter());
After adding serialize or deserialize base class object as below
JsonConvert.DeserializeObject<Type>("json string", jsonSerializerSettings );
I had a similar issue, and I solved it with another way, maybe this would help someone:
I have json that contains in it several fields that are always the same, except for one field called "data" that can be a different type of class every time.
I would like to de-serialize it without analayzing every filed specific.
My solution is:
To define the main class (with 'Data' field) with , the field Data is type T.
Whenever that I de-serialize, I specify the type:
MainClass:
public class MainClass<T>
{
[JsonProperty("status")]
public Statuses Status { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("data")]
public T Data { get; set; }
public static MainClass<T> Parse(string mainClsTxt)
{
var response = JsonConvert.DeserializeObject<MainClass<T>>(mainClsTxt);
return response;
}
}
User
public class User
{
[JsonProperty("id")]
public int UserId { get; set; }
[JsonProperty("first_name")]
public string FirstName { get; set; }
[JsonProperty("last_name")]
public string LastName { get; set; }
}
Product
public class Product
{
[JsonProperty("product_id")]
public int ProductId { get; set; }
[JsonProperty("product_name")]
public string ProductName { get; set; }
[JsonProperty("stock")]
public int Stock { get; set; }
}
Using
var v = MainClass<User>.Parse(userJson);
var v2 = MainClass<Product>.Parse(productJson);
json example
userJson: "{"status":1,"description":"my description","data":{"id":12161347,"first_name":"my fname","last_name":"my lname"}}"
productJson: "{"status":1,"description":"my description","data":{"product_id":5,"product_name":"my product","stock":1000}}"
public abstract class JsonCreationConverter<T> : JsonConverter
{
protected abstract T Create(Type objectType, JObject jObject);
public override bool CanConvert(Type objectType)
{
return typeof(T) == objectType;
}
public override object ReadJson(JsonReader reader,Type objectType,
object existingValue, JsonSerializer serializer)
{
try
{
var jObject = JObject.Load(reader);
var target = Create(objectType, jObject);
serializer.Populate(jObject.CreateReader(), target);
return target;
}
catch (JsonReaderException)
{
return null;
}
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Now implement this interface
public class SportActivityConverter : JsonCreationConverter<BaseSportActivity>
{
protected override BaseSportActivity Create(Type objectType, JObject jObject)
{
BaseSportActivity result = null;
try
{
switch ((ESportActivityType)jObject["activityType"].Value<int>())
{
case ESportActivityType.Football:
result = jObject.ToObject<FootballActivity>();
break;
case ESportActivityType.Basketball:
result = jObject.ToObject<BasketballActivity>();
break;
}
}
catch(Exception ex)
{
Debug.WriteLine(ex);
}
return result;
}
}
I have this classes:
public class CTest
{
public int Id { get; set; }
public string Name { get; set; }
public List<Class2> Details { get; set; }
}
public class Class2
{
public int Id { get; set; }
public int Data1 { get; set; }
}
I know that I can deserialize using JsonConvert.DeserializeObject<T>(myObject); even Lists, but in Class2, I'd like to know how to filter if Id > 0.
For example, I'd like to get this result:
{
"id":5,
"name":"test",
"details":[
{
"id":1,
"data1":0.25
}
]
}
Instead of:
{
"id":5,
"name":"test",
"details":[
{
"id":12,
"data1":0.25
},
{
"id":0,
"data1":0.0
},
]
}
You can use Custom JsonConverter
class ListClass2Converter : JsonConverter<List<Class2>>
{
public override List<Class2> ReadJson(JsonReader reader, Type objectType,
[AllowNull] List<Class2> existingValue, bool hasExistingValue,
JsonSerializer serializer)
{
var list = new List<Class2>();
while (reader.Read() && reader.TokenType != JsonToken.EndArray)
{
if (reader.TokenType == JsonToken.StartObject)
{
var item = serializer.Deserialize<Class2>(reader);
if (item.Id != 0)
list.Add(item);
}
}
return list;
}
public override void WriteJson(JsonWriter writer, [AllowNull] List<Class2> value,
JsonSerializer serializer)
{
serializer.Serialize(writer, ((List<Class2>)value).Where(c => c.Id != 0).ToArray());
}
}
Read code
var json = File.ReadAllText("test.json");
var сTest = JsonConvert.DeserializeObject<CTest>(json, new ListClass2Converter());
Write code
var json = JsonConvert.SerializeObject(
cTest, Formatting.Indented, new ListClass2Converter());
You can use as many converters for different types of collections as you need.
I have following json:
{"EventMessageUId":"ef51b5a3-32b2-e611-baf9-fc3fdb446bd2","Message":
"{\"StoryId\":20500,\"StoryDesc\":\"Test Story
data\"}","ProjectId":"1"}
Below is the class in which I'm trying to map it:
public class Requirments
{
public int FileID { get; set; }
public string EventMessageUId { get; set; }
public string ProjectId { get; set; }
public List<Message> Message { get; set; }
//public object[] Message { get; set; }
public string error { get; set; }
}
It is taking message tag as a string
"Message": "{\"StoryId\":20500,\"StoryDesc\":\"Test Story data\"}"
I want to Map it into List<Message>
public class Message
{
public string StoryID { get; set; }
public string StoryDesc { get; set; }
}
What can I do for this without changing json?
In the current scenario it gives me an error when I try it with List<Message>
This might do the trick for you
string jsonstr = File.ReadAllText(YourJSONFile);
jsonstr = jsonstr.Replace("\"{", "{");
jsonstr = jsonstr.Replace("}\"", "}");
jsonstr = jsonstr.Replace("\\", "");
var ser = JsonConvert.DeserializeObject<MyMessages>(jsonstr);
The classes would look like
public class Message
{
[JsonProperty("StoryId")]
public int StoryId { get; set; }
[JsonProperty("StoryDesc")]
public string StoryDesc { get; set; }
}
public class MyMessages
{
[JsonProperty("Message")]
public Message Message { get; set; }
}
The problem with the JSON is
"Message": "{\"StoryId\":20500,\"StoryDesc\":\"Test Story data\"}"
^ ^
are these " which is making it a string instead of two different properties of JSON. So we removed that "{ and }" with
jsonstr = jsonstr.Replace("\"{", "{");
jsonstr = jsonstr.Replace("}\"", "}");
and now the remaining JSON string will be
"Message": {\"StoryId\":20500,\"StoryDesc\":\"Test Story data\"}
^ ^ ^ ^ ^ ^
in which we have \ back slash in the JSON string which will again creates issue while deseralizing the JSON string. So
jsonstr = jsonstr.Replace("\\", "");
You have two different problems in deserializing your JSON to your Requirments class:
The Message property contains nested double-serialized data. I.e. the sending system serialized the original Message object to a JSON string then included that in an outer container object that was subsequently itself serialized, causing the inner JSON data to be escaped.
The nested JSON represents a single object - a collection of name/value pairs surrounded by braces. But you want to deserialize to a List<Message>, and all JSON serializers will map a List<T> to a JSON array rather than to a JSON object.
Both these problems can be overcome by using json.net combined with a custom JsonConverter for the Message property. However, the conversion will require two independent steps:
You will need to unwrap the nested double-serialized JSON into a string.
And then map the JSON object thereby unwrapped with a converter similar to SingleOrArrayConverter<T> from How to handle both a single item and an array for the same property using JSON.net.
The following set of converters performs this chained conversion:
public class SingleOrArrayConverter<TCollection, TItem> : SingleOrArrayConverter where TCollection : ICollection<TItem>
{
public override bool CanConvert(Type objectType)
{
if (!base.CanConvert(objectType))
return false;
return typeof(TCollection).IsAssignableFrom(objectType);
}
}
public class SingleOrArrayConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
if (objectType.IsArray || objectType == typeof(string) || objectType.IsPrimitive)
return false;
Type elementType = null;
foreach (var type in objectType.GetCollectItemTypes())
{
if (elementType == null)
elementType = type;
else
return false;
}
return elementType != null;
}
object ReadJsonGeneric<TItem>(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var collection = (ICollection<TItem>)(existingValue ?? serializer.ContractResolver.ResolveContract(objectType).DefaultCreator());
if (reader.TokenType == JsonToken.StartArray)
serializer.Populate(reader, collection);
else
collection.Add(serializer.Deserialize<TItem>(reader));
return collection;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
if (objectType.IsArray)
throw new JsonSerializationException("Read-only collections such as arrays are not supported");
try
{
var elementType = objectType.GetCollectItemTypes().SingleOrDefault();
if (elementType == null)
throw new JsonSerializationException(string.Format("{0} is not an ICollection<T> for some T", objectType));
var method = typeof(SingleOrArrayConverter).GetMethod("ReadJsonGeneric", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
return method.MakeGenericMethod(new[] { elementType }).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);
}
}
void WriteJsonGeneric<TItem>(JsonWriter writer, object value, JsonSerializer serializer)
{
var list = (ICollection<TItem>)value;
if (list.Count == 1)
{
foreach (object item in list)
{
serializer.Serialize(writer, item);
break;
}
}
else
{
writer.WriteStartArray();
foreach (var item in list)
{
serializer.Serialize(writer, item);
}
writer.WriteEndArray();
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var objectType = value.GetType();
try
{
var elementType = objectType.GetCollectItemTypes().SingleOrDefault();
if (elementType == null)
throw new JsonSerializationException(string.Format("{0} is not an ICollection<T> for some T", objectType));
var method = typeof(SingleOrArrayConverter).GetMethod("WriteJsonGeneric", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
method.MakeGenericMethod(new[] { elementType }).Invoke(this, new object[] { writer, value, serializer });
}
catch (Exception ex)
{
// Wrap the TargetInvocationException in a JsonSerializerException
throw new JsonSerializationException("Failed to serialize " + objectType, ex);
}
}
}
public static class TypeExtensions
{
public static IEnumerable<Type> GetInterfacesAndSelf(this Type type)
{
if (type == null)
throw new ArgumentNullException();
if (type.IsInterface)
return new[] { type }.Concat(type.GetInterfaces());
else
return type.GetInterfaces();
}
public static IEnumerable<Type> GetCollectItemTypes(this Type type)
{
foreach (Type intType in type.GetInterfacesAndSelf())
{
if (intType.IsGenericType
&& intType.GetGenericTypeDefinition() == typeof(ICollection<>))
{
yield return intType.GetGenericArguments()[0];
}
}
}
}
public class StringConverterDecorator : JsonConverterDecorator
{
public StringConverterDecorator(Type jsonConverterType) : base(jsonConverterType) { }
public StringConverterDecorator(JsonConverter converter) : base(converter) { }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
// Unwrap the double-serialized string.
var s = JToken.Load(reader).ToString();
var token = JToken.Parse(s);
// Then convert the revealed JSON to its final form.
using (var subReader = token.CreateReader())
{
while (subReader.TokenType == JsonToken.None)
subReader.Read();
return base.ReadJson(subReader, objectType, existingValue, serializer);
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
string s;
// Serialize the value to an intermediate JSON string.
using (var textWriter = new StringWriter())
{
using (var subWriter = new JsonTextWriter(textWriter))
{
base.WriteJson(subWriter, value, serializer);
}
s = textWriter.ToString();
}
// Then double-serialize the value by writing the JSON as a string literal to the output stream.
writer.WriteValue(s);
}
}
public abstract class JsonConverterDecorator : JsonConverter
{
readonly JsonConverter converter;
public JsonConverterDecorator(Type jsonConverterType) : this((JsonConverter)Activator.CreateInstance(jsonConverterType)) { }
public JsonConverterDecorator(JsonConverter converter)
{
if (converter == null)
throw new ArgumentNullException();
this.converter = converter;
}
public override bool CanConvert(Type objectType)
{
return converter.CanConvert(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return converter.ReadJson(reader, objectType, existingValue, serializer);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
converter.WriteJson(writer, value, serializer);
}
public override bool CanRead { get { return converter.CanRead; } }
public override bool CanWrite { get { return converter.CanWrite; } }
}
Then apply the chained converter to your Message property using a [JsonConverter(typeof(TConverter), ...)] attribute as follows:
public class Requirments
{
public int FileID { get; set; }
public string EventMessageUId { get; set; }
public string ProjectId { get; set; }
[JsonConverter(typeof(StringConverterDecorator), typeof(SingleOrArrayConverter))]
public List<Message> Message { get; set; }
public string error { get; set; }
}
Then deserialize with JsonConvert.DeserializeObject<T>:
var requirement = JsonConvert.DeserializeObject<Requirments>(jsonString);
Or, if you do not want to apply the converter to directly to your type, you can add it to JsonSerializerSettings.Converters and deserialize as follows:
var settings = new JsonSerializerSettings
{
Converters = { new StringConverterDecorator(new SingleOrArrayConverter<List<Message>, Message>()) },
};
var requirement = JsonConvert.DeserializeObject<Requirments>(json, settings);
Note the generic SingleOrArrayConverter<List<Message>, Message> is required here to prevent the converter from applying to all types of collection.
Sample fiddle.
The definition of the Message class is right. However the Json body for message property is not an array. So the class should be
public class Requirments
{
public int FileID { get; set; }
public string EventMessageUId { get; set; }
public string ProjectId { get; set; }
public Message Message { get; set; }
//public object[] Message { get; set; }
public string error { get; set; }
}
Main problem is with your JSON, it should look like this
{"EventMessageUId":"ef51b5a3-32b2-e611-baf9-fc3fdb446bd2","Message":
[{"StoryId":20500,"StoryDesc":"Test Story
data"}],"ProjectId":"1"}
After that you will get "Message" as a list, and also you can easily map it to class.
public class Message
{
public int StoryId { get; set; }
public string StoryDesc { get; set; }
}
public class Requirments
{
public string EventMessageUId { get; set; }
public List<Message> Message { get; set; }
public string ProjectId { get; set; }
}
I have successfully parsed list of class type Message from your Json but for that you will slightly need to change your class Requirements:
public class Requirments
{
public int FileID { get; set; }
public string EventMessageUId { get; set; }
public string ProjectId { get; set; }
public string Message { get; set; }
//public List<Message> Message { get; set; } // **need to change property type to "string"**
//public object[] Message { get; set; }
public string error { get; set; }
}
You can try below code:
Requirments mainResult = JsonConvert.DeserializeObject<Requirments>("YOUR JSON STING");
List<Message> MessageList = JsonConvert.DeserializeObject<List<Message>>(mainResult.Message.ToString());
Note: you will need to include using Newtonsoft.Json; in your class.
This will give you list of class type Message in MessageList
Hope, this will help!
My application accepts long JSON templates from clients that I deserialize and process. I would like to provide better error handling information that contains lineNumber of invalid objects in the JSON text to customers. Note that this is for errors that occur in post-processing and NOT for errors that occur during deserialization as this is already handled by the Newtonsoft.
As an example, I have the below JSON and its corresponding .Net type
{
"Version": "1.0.0.0",
"MyComplexObject": [
{
"Prop1": "Val1",
"Prop2": "Val2",
"Prop3": 1
}
]
}
public class MyComplexObject
{
[JsonProperty]
public string Prop1 { get; set; }
[JsonProperty]
public string Prop2 { get; set; }
[JsonProperty]
public int Prop3 { get; set; }
**public int LineNumber;
public int LinePosition;**
}
public class RootObject
{
[JsonProperty]
public string Version { get; set; }
[JsonProperty]
public List<MyComplexObject> MyComplexObject { get; set; }
}
I would like the LineNumber and LinePosition properties to be populated at deserialization so that it may be used at a later time. I am currently deserializing the JSON using the below code
JsonConvert.DeserializeObject<RootObject>(value: rawJson,settings: mysettings);
Appreciate any responses
I was able to get around this by implementing a custom converter like below. Any class that implements JsonLineInfo will automatically get the line number info for itself and its properties when it is deserialized.
public class LineInfo
{
[JsonIgnore]
public int LineNumber { get; set;}
[JsonIgnore]
public int LinePosition { get; set;}
}
public abstract class JsonLineInfo : LineInfo
{
[JsonIgnore]
public Dictionary<string, LineInfo> PropertyLineInfos { get; set; }
}
class LineNumberConverter : JsonConverter
{
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException("Converter is not writable. Method should not be invoked");
}
public override bool CanConvert(Type objectType)
{
return typeof(JsonLineInfo).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType != JsonToken.Null)
{
int lineNumber = 0;
int linePosition = 0;
var jsonLineInfo = reader as IJsonLineInfo;
if (jsonLineInfo != null && jsonLineInfo.HasLineInfo())
{
lineNumber = jsonLineInfo.LineNumber;
linePosition = jsonLineInfo.LinePosition;
}
var rawJObject = JObject.Load(reader);
var lineInfoObject = Activator.CreateInstance(objectType) as JsonLineInfo;
serializer.Populate(this.CloneReader(reader, rawJObject), lineInfoObject);
return this.PopulateLineInfo(
lineInfoObject: lineInfoObject,
lineNumber: lineNumber,
linePosition: linePosition,
rawJObject: rawJObject);
}
return null;
}
private JsonReader CloneReader(JsonReader reader, JObject jobject)
{
var clonedReader = jobject.CreateReader();
clonedReader.Culture = reader.Culture;
clonedReader.DateParseHandling = reader.DateParseHandling;
clonedReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
clonedReader.FloatParseHandling = reader.FloatParseHandling;
clonedReader.MaxDepth = reader.MaxDepth;
return clonedReader;
}
private object PopulateLineInfo(JsonLineInfo lineInfoObject, int lineNumber, int linePosition, JObject rawJObject)
{
if (lineInfoObject != null)
{
lineInfoObject.PropertyLineInfos = new Dictionary<string, LineInfo>(StringComparer.InvariantCultureIgnoreCase);
lineInfoObject.LineNumber = lineNumber;
lineInfoObject.LinePosition = linePosition;
foreach (var property in rawJObject.Properties().CoalesceEnumerable())
{
var propertyLineInfo = property as IJsonLineInfo;
if (propertyLineInfo != null)
{
lineInfoObject.PropertyLineInfos.Add(
property.Name,
new LineInfo
{
LineNumber = propertyLineInfo.LineNumber,
LinePosition = propertyLineInfo.LinePosition
});
}
}
}
return lineInfoObject;
}
}
Here's how I handled this to handle error when using converters to get references to data:
static string GetPosition(JsonReader reader)
{
if (reader is JsonTextReader textReader)
return $"{CurrentFilename}({textReader.LineNumber},{textReader.LinePosition}):";
else
return $"{CurrentFilename}({reader.Path}):";
}
public class AudioClipConverter : JsonConverter<AudioClip>
{
public override void WriteJson(JsonWriter writer, AudioClip value, JsonSerializer serializer)
{
writer.WriteValue(value.name);
}
public override AudioClip ReadJson(JsonReader reader, Type objectType, AudioClip existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var value = (string)reader.Value;
if (String.IsNullOrEmpty(value))
return null;
var result = Instance.GetAudioClip(value);
if (result == null)
Debug.LogWarning($"{GetPosition(reader)} AudioClip {value} not found in database.");
return result;
}
}