Custom object serialization - c#

Is there a way to change how an object is serialized in json? for example, the following object:
class PersonName {
public string Name { get; set; }
}
var obj = new {
pName = new PersonName {
Name = "Andrew"
}
}
is serialized as {"pName": { "name": "Andrew" }}.
But is there a way to serialize it as {"pName": "Andrew"}?

This should work:
class PersonName
{
[JsonProperty(PropertyName = "pName")]
public string Name { get; set; }
}
var obj = new PersonName { Name = "Andrew" }

So finally I've managed to get the answer to this one.
A custom converter is needed:
public class PersonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var person = value as PersonName;
if (person == null)
{
return;
}
serializer.Serialize(writer, person.Name);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var constructorInfo = objectType.GetTypeInfo().GetConstructors(BindingFlags.Public | BindingFlags.Instance).Single();
var parameterInfo = constructorInfo.GetParameters().Single();
var parameterType = parameterInfo.ParameterType;
var value = serializer.Deserialize(reader, parameterType);
return Activator.CreateInstance(objectType, new[] { value });
}
public override bool CanConvert(Type objectType)
{
return typeof(PersonName).GetTypeInfo().IsAssignableFrom(objectType);
}
}
which can be used like this:
[JsonConverter(typeof(PersonConverter))]
public class PersonName
{
public PersonName(string name)
{
Name = name;
}
public string Name { get; set; }
}

Related

How to deserialize nested array in my case to custom classes?

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

How to define string to object/dictionary in JSON

I create a class for define my request, I don't get the accepted JSON string
I define this object:
public class Request
{
public Var_Args[] var_args { get; set; }
}
public class Var_Args
{
public object title { get; set; }
public object owner { get; set; }
}
when I convert it to json, I get the following string:
{"requests":[{"var_args":[{"title":"Test","owner":"skaner"}]}]}
how can I define the class, for get the accepted json string:
{"requests":[{"var_args":[{"title":"Test"},{"owner":"skaner"}]}]}
You can write a custom JSON converter that can serialize every property of an object (of a known type) into a different JSON object.
public class PropertyAsObjectConverter : JsonConverter
{
private readonly Type[] _types;
public PropertyAsObjectConverter(params Type[] types)
{
_types = types;
}
public override bool CanConvert(Type objectType)
{
return _types.Any(t => t == objectType);
}
public override bool CanRead
{
get { return false; }
}
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)
{
var properties = value.GetType().GetProperties(BindingFlags.Public|BindingFlags.Instance);
foreach(var property in properties)
{
var name = property.Name;
var attrs = property.GetCustomAttributes(typeof(JsonPropertyAttribute));
if(attrs != null)
{
if (attrs.FirstOrDefault() is JsonPropertyAttribute attr)
name = attr.PropertyName;
}
writer.WriteStartObject();
writer.WritePropertyName(name);
serializer.Serialize(writer, property.GetValue(value));
writer.WriteEndObject();
}
}
}
This implements only the serialization, but you can extend it to support deserialization too. You can also extend it to serialize fields should you need that.
You can then define your classes as follows. Notice that I am using JsonPropertyAttribute here to specify the name in the serialized JSON.
public class Content
{
[JsonProperty("requests")]
public Request Value { get; set; }
}
public class Request
{
[JsonProperty("var_args")]
public VarArgs[] Arguments { get; set; }
}
public class VarArgs
{
[JsonProperty("title")]
public object Title { get; set; }
[JsonProperty("owner")]
public object Owner { get; set; }
}
This is how you can use it:
static void Main(string[] args)
{
var request = new Content()
{
Value = new Request()
{
Arguments = new VarArgs[]
{
new VarArgs()
{
Title = "Test",
Owner = "Skaner",
}
}
}
};
var text = JsonConvert.SerializeObject(
request,
Formatting.None,
new PropertyAsObjectConverter(typeof(VarArgs)));
Console.WriteLine(text);
}
The output for this sample is the one you expect:
{"requests":{"var_args":[{"title":"Test"},{"owner":"Skaner"}]}}
You could use a custom JsonConverter like the below.
It takes the Var_Args object and splits it in two different JObject which correspond to two different JSON objects.
public class VarArgsConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var obj = (JObject)JToken.FromObject(value);
var objTitle = new JObject();
objTitle.Add("title", obj.GetValue("title"));
var objOwner = new JObject();
objOwner.Add("owner", obj.GetValue("owner"));
objTitle.WriteTo(writer);
objOwner.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
}
public override bool CanRead
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Var_Args);
}
}
public class Wrapper
{
[JsonProperty("requests")]
public Request Requests { get; set; }
}
public class Request
{
public Var_Args[] var_args { get; set; }
}
public class Var_Args
{
public object title { get; set; }
public object owner { get; set; }
}
Then use it:
var wrapper = new Wrapper();
var request = new Request();
request.var_args = new Var_Args[] {
new Var_Args(){ title = "Test", owner = "skaner" },
new Var_Args(){ title = "Test2", owner = "skaner2" }
};
wrapper.Requests = request;
var serialized = JsonConvert.SerializeObject(wrapper, new VarArgsConverter());
Output
{"requests":{"var_args":[{"title":"Test"},{"owner":"skaner"},{"title":"Test2"},{"owner":"skaner2"}]}}
Note: I'm using the Wrapper class just to produce the requested JSON.
If you don't want to specify the converter each time, you can register your converter globally. Please see this answer which explains how you can do that. So, the serializer will use your custom JsonConverter every time you try to serialize a Var_Args object.
If you register the JsonConvert globally you can use:
var serialized = JsonConvert.SerializeObject(wrapper);
You can use System.Reflection to redefine Var_Args as an implementation of the IEnumerable<Dictionary<string,object>> interface by adding two methods to the class:
public class Var_Args : IEnumerable<Dictionary<string,object>>
{
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
public IEnumerator<Dictionary<string,object>> GetEnumerator()
{
var Properties = GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var Property in Properties)
{
var Entry = new Dictionary<string,object>();
Entry.Add(Property.Name, Property.GetValue(this));
yield return Entry;
}
}
public object title { get; set; }
public object owner { get; set; }
}
While Reflection may be regarded as slow, there is a technique you can use to statically compile an IEnumerable at runtime so that the reflection only occurs once for the definition of the class, like this:
public class Var_Args : IEnumerable<Dictionary<string,object>>
{
private struct PropertyList<T>
{
public static readonly List<Func<T,Dictionary<string,object>>> PropertyGetters;
static PropertyList()
{
PropertyGetters = new List<Func<T,Dictionary<string,object>>>();
var Properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var Property in Properties)
{
var Args = new [] { Expression.Parameter(typeof(T)) };
var Key = Property.Name;
var Value = Expression.Property(Args[0], Property);
Func<T,object> Get = Expression.Lambda<Func<T,object>>(Value, Args).Compile();
PropertyGetters.Add(obj =>
{
var entry = new Dictionary<string,object>();
entry.Add(Key, Get(obj));
return entry;
});
}
}
}
protected static IEnumerable<Dictionary<string,object>> GetPropertiesAsEntries<T>(T obj)
{
return PropertyList<T>.PropertyGetters.Select(f => f(obj));
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
public IEnumerator<Dictionary<string,object>> GetEnumerator()
{
return GetPropertiesAsEntries(this).GetEnumerator();
}
public object title { get; set; }
public object owner { get; set; }
}

JsonConvert deserialize an array of abstract classes

Let's say I have the following class structure (Building is an abstract class):
public class Street
{
public string StreetName { get; set; }
public Building[] Buildings { get; set; }
}
public abstract class Building
{
public string Name { get; set; }
}
public class House : Building
{
public int Floors { get; set; }
}
public class Flat : Building
{
public int WhichFloor { get; set; }
}
I then create a street object with a few flats in the buildings array:
Flat f1 = new Flat { Name = "Flat 1", WhichFloor = 1 };
Flat f2 = new Flat { Name = "Flat 2", WhichFloor = 2 };
Street street = new Street
{
StreetName = "Street Name",
Buildings = new[] { f1, f2 }
};
Using JsonConvert I then Serialize the object:
var toJson = JsonConvert.SerializeObject(street);
Now I want to convert the json back to a street object:
var fromJson = JsonConvert.DeserializeObject<Street>(toJson);
This fails with the following error:
"Could not create an instance of type Building. Type is an interface or abstract class and cannot be instantiated. Path 'Buildings[0].WhichFloor'"
How can I tell the JsonConvert class that Buildings should be an array of flats?
As per #Evk's shared link, you should try setting TypeNameHandling to TypeNameHandling.Auto while serializing and deserializing:
var toJson = JsonConvert.SerializeObject(street, Newtonsoft.Json.Formatting.Indented, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto
});
var fromJson = JsonConvert.DeserializeObject<Street>(toJson, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto
});
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;
}
}

Map Json to custom class

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!

Newtonsoft.Json - Getting corresponding line numbers of deserialized objects from JSON for better error handling

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

Categories