Deserialize property to different class based on other property c# - c#

I know there are many similar questions on SO, however all the ones I've found require a shared base class in order to work.
With a stream of JSON data like this:
[
{
"webhookType": "order",
"data": {
"id": "eeiefj393",
"orderProperty": "Value"
}
},
{
"webhookType": "customer",
"data": {
"id": 29238,
"customerProperty": "Value"
}
}
]
I wish to deserialize this into two containers, List<Customer> and List<Order>. Where the two classes are as follows:
class Order
{
public string Id { get; set; }
public string OrderProperty { get; set; }
[...]
}
class Customer
{
public long Id { get; set; }
public string CustomerProperty { get; set; }
[...]
}
There may be shared property names however there are no shared properties + type between these two classes and so the solutions involing a sub class aren't working for me.

You need to create a JsonConverter.
DataConverter
public class WebHookConverter : JsonConverter
{
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.StartObject)
{
JObject item = JObject.Load(reader);
if (item["webhookType"].Value<string>() == "order")
{
var webhook = new WebHook
{
Type = item["webhookType"].Value<string>(),
Data = item["data"].ToObject<Order>()
};
return webhook;
}
else if (item["webhookType"].Value<string>() == "customer")
{
var webhook = new WebHook
{
Type = item["webhookType"].Value<string>(),
Data = item["data"].ToObject<Customer>()
};
return webhook;
}
}
return null;
}
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}
}
Objects
[JsonConverter(typeof(WebHookConverter))]
public class WebHook
{
[JsonProperty("webhookType")]
public string Type { get; set; }
public object Data { get; set; }
}
public class Order
{
public string Id { get; set; }
[JsonProperty("orderProperty")]
public string Property { get; set; }
}
public class Customer
{
public long Id { get; set; }
[JsonProperty("customerProperty")]
public string Property { get; set; }
}
Serialization
var json = File.ReadAllText("json1.json");
var obj = JsonConvert.DeserializeObject<List<WebHook>>(json);
var orderList = obj.Where(o => o.Type == "order").Select(o => o.Data).ToList();
var customerList = obj.Where(o => o.Type == "customer").Select(o => o.Data).ToList();
Output:

Related

How Can I deserialize a json into a structure?

I have a class mapping like this:
public class Settings
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("content")]
public ContentStructure Content { get; set; }
}
public struct ContentStructure
{
public Content ContentClass;
public string ContentString;
public static implicit operator ContentStructure(Content content) => new ContentStructure { ContentClass = content };
public static implicit operator ContentStructure(string #string) => new ContentStructure { ContentString = #string };
}
public class Content
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("duration")]
public long Duration { get; set; }
}
When I attempt to deserialize the following JSON string:
{
"id": "any_id",
"type": "any_type",
"content": {
"id": "any_id",
"duration": 1000
}
}
I always get the deserialized settings object with the property settings.Content.ContentClass null, but whenever my JSON string has the property "content" as string (instead of an object) the structure field ContentString comes correctly. What I am doing wrong? How can I can convert the JSON string above correctly?
Yet another solution could be to make use of the JsonSchema
First let's redefine your data model:
public abstract class Settings
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
}
public class SettingsV1 : Settings
{
[JsonProperty("content")]
public string Content { get; set; }
}
public class SettingsV2 : Settings
{
[JsonProperty("content")]
public Content Content { get; set; }
}
public class Content
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("duration")]
public long Duration { get; set; }
}
Instead of having a middle man (ContentStructure) rather than you can have two separate Settings versions
The common fields are defined inside the abstract base class
Now, you can use these two versioned classes to define json schemas:
private static JSchema schemaV1;
private static JSchema schemaV2;
//...
var generator = new JSchemaGenerator();
schemaV1 = generator.Generate(typeof(SettingsV1));
schemaV2 = generator.Generate(typeof(SettingsV2));
Finally all you need to do is to do a preliminary check before calling the DeserializeObject with the proper type:
Settings settings = null;
var semiParsed = JObject.Parse(json);
if (semiParsed.IsValid(schemaV1))
{
settings = JsonConvert.DeserializeObject<SettingsV1>(json);
}
else if (semiParsed.IsValid(schemaV2))
{
settings = JsonConvert.DeserializeObject<SettingsV2>(json);
}
else
{
throw new NotSupportedException("The provided json format is not supported");
}
Use a Custom JsonConverter. Modify it based on your need.
[JsonConverter(typeof(ContentStructureConverter))]
public struct ContentStructure
{
public Content ContentClass;
public string ContentString;
public static implicit operator ContentStructure(Content content) => new ContentStructure { ContentClass = content };
public static implicit operator ContentStructure(string #string) => new ContentStructure { ContentString = #string };
}
public class ContentStructureConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ContentStructure);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
ContentStructure contentStruct;
if (reader.ValueType == typeof(string))
contentStruct = reader.Value as string;
else
contentStruct = serializer.Deserialize<Content>(reader);
return contentStruct;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
ContentStructure? contentStruct = value as ContentStructure?;
if (contentStruct.HasValue && contentStruct.Value.ContentClass != null)
serializer.Serialize(writer, contentStruct.Value.ContentClass);
else
serializer.Serialize(writer, contentStruct.Value.ContentString);
}
}
The input json is wrong for your format.
If you use this json instead,
{
"id": "any_id",
"type": "any_type",
"content": {
"ContentClass" : {
"id": "any_id",
"duration": 1000
}
}
}
This way, it will work.
settings.Content.ContentClass is three layer but your is json two layer (settings.Content). So after "content", it is looking for id and duration which ContentStructure does not have these two fields.
My reasoning is when it encounters a value type (like {"field" : "value}), it will look for value type, like string, int or double. When it encounters a json type (like {"field" : {another json here} }), it will look for class or struct.

How to discard JSON elements with specific values while deserializing

This is an interesting situation. My application saves and loads JSON data from MongoDB, which works fine 95% of the time. The JSON data in such cases is like the following:
{
"isDemo": true,
"CustomerReference": "nabTest",
"Fee": null,
"OrderId": "48/XYZ3",
"Asynchronous": false,
}
The remaining 5% of the time,a legacy application loads some XML data, converts that into JSON format and inserts that into the same MongoDB collection. The JSON data in such cases is like this:
{
"#xmlns:xsi":"http://www.w3.org/2001/XMLSchema-instance",
"#xmlns:xsd":"http://www.w3.org/2001/XMLSchema",
"#xmlns":"http://MyDomain.com.au/SomeService.Contract/2007/06/18",
"isDemo":{
"#xmlns":"http://MyDomain.com.au/ABC.Services",
"#text":"true"
},
"Asynchronous":{
"#xmlns":"http://MyDomain.com.au/ABC.Services"
"#text":"false"
},
"Fee":{
"#xsi:nil":"true",
"#xmlns":"http://MyDomain.com.au/ABC.Services"
},
"CustomerReference":{
"#xmlns":"http://MyDomain.com.au/ABC.Services"
},
"OrderId":"48/XYZ3"
}
In such cases, Newtonsoft deserializer crashes with the following exception:
Newtonsoft.Json.JsonReaderException: 'Unexpected character encountered
while parsing value: {. Path 'CustomerReference', line 1, position
919.'
The minimum code for this is the following:
// 'result' is of type object, loaded from MongoDB
var resultStr = JsonConvert.SerializeObject(result);
var obj = JsonConvert.DeserializeObject<T>(resultStr, jsonSerializerSettings);
What is an elegant way to handle this situation, with minimal code changes.
Your Object representation for the normal Json should be something like this :
using J = Newtonsoft.Json.JsonPropertyAttribute;
public partial class Data
{
[J("isDemo")] public bool? IsDemo { get; set; }
[J("Asynchronous")] public bool? IsAsynchronous { get; set; }
[J("OrderId")] public string OrderId { get; set; }
[J("CustomerReference")] public string CustomerReference { get; set; }
[J("Fee")] public double? Fee { get; set; }
}
While having a custom JsonConverter handeling the whole Xml representation will be nice. I find it difficult to write and maintain.
You should divide and conquer:
Generate a class that is the representation of the Xml-ish Json.
Remove unnecessary property, keep Xml "nil" where needed
Create simple custom converter for mapping string representation to the correct type.
eg: "true" => bool, "123" => int,
public partial class XmlRepresentation
{
[J("isDemo")] public BoolWrapper IsDemo { get; set; }
[J("Asynchronous")] public BoolWrapper Asynchronous { get; set; }
[J("Fee")] public DoubleWrapper Fee { get; set; }
[J("CustomerReference")] public StringWrapper CustomerReference { get; set; }
[J("OrderId")] public string OrderId { get; set; }
}
public partial class NullableXmlValue
{
[J("#xsi:nil")][JsonConverter(typeof(BoolParseStringConverter))]
public bool IsNull { get; set; }
}
public partial class BoolWrapper :NullableXmlValue
{
[J("#text")][JsonConverter(typeof(BoolParseStringConverter))]
public bool Value { get; set; }
}
public partial class StringWrapper :NullableXmlValue
{
[J("#text")] public string Value { get; set; }
}
public partial class DoubleWrapper :NullableXmlValue
{
[J("#text")][JsonConverter(typeof(DoubleParseStringConverter))]
public double Value { get; set; }
}
With some simple String to Type {Bool, Double} parser:
internal class BoolParseStringConverter : JsonConverter
{
public override bool CanConvert(Type t) => t == typeof(bool) || t == typeof(bool?);
public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null) return null;
var value = serializer.Deserialize<string>(reader);
if (Boolean.TryParse(value, out bool b))
{
return b;
}
throw new Exception("Cannot unmarshal type bool");
}
public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
{
if (untypedValue == null)
{
serializer.Serialize(writer, null);
return;
}
var value = (bool)untypedValue;
var boolString = value ? "true" : "false";
serializer.Serialize(writer, boolString);
return;
}
public static readonly BoolParseStringConverter Singleton = new BoolParseStringConverter();
}
internal class DoubleParseStringConverter : JsonConverter
{
public override bool CanConvert(Type t) => t == typeof(double) || t == typeof(double?);
public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null) return null;
var value = serializer.Deserialize<string>(reader);
if (Double.TryParse(value, out double l))
{
return l;
}
throw new Exception("Cannot unmarshal type long");
}
public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
{
if (untypedValue == null)
{
serializer.Serialize(writer, null);
return;
}
var value = (double)untypedValue;
serializer.Serialize(writer, value.ToString());
return;
}
public static readonly DoubleParseStringConverter Singleton = new DoubleParseStringConverter();
}
This way you can directly deserialize to XmlRepresentation and use a simple projection logic to get the Data.
public partial class XmlRepresentation
{
public Data ToDataType(){
var result = new Data();
if( !this.IsDemo?.IsNull ?? false)
result.IsDemo = this.IsDemo.Value;
// etc..
return result;
}
}
And for knowing if it's the normal Json or the Xml-ish one I will simply use a try catch.
That way I won't have to write complexe JsonConverter or class that represent both Normal and Xml-ish.
Please use like this in Model:
public class User
{
// always require a string value
[JsonProperty("name", Required = Required.Always)]
public string Name { get; set; }
// don't require any value
[JsonProperty("role", NullValueHandling = NullValueHandling.Ignore)]
public string Role { get; set; }
// property is ignored
[JsonIgnore]
public string Password { get; set; }
}
Result generated with Json.NET serialization attributes:
{
"type": "object",
"properties": {
"name": {
"type": "string"
},
"role": {
"type": [
"string",
"null"
]
}
},
"required": [
"name"
]
}

Deserializing complex Json objects

I want to deserialize a complex and let's say not well constructed json. That code that I wrote doesn't deserialize the object, the MovieInfo property is null. You can find the example json in the code. I want to avoid using JObject.Parse and dynamic objects. What am I missing here?
using System.Collections.Generic;
using Newtonsoft.Json;
namespace ComplexJsonExample
{
class Program
{
static void Main(string[] args)
{
string jsonInText = #"
{
""movies"" : [
{
""Harry Potter"" : [
{ ""rating"": ""great""},
{ ""rating"": ""horrible""}
]
},
{
""Guardians of the galaxy"" : [
{ ""rating"": ""cool""},
{ ""rating"": ""awesome""}
]
}
]
}
";
var movieList = JsonConvert.DeserializeObject<MovieList>(jsonInText);
}
}
public class MovieList
{
[JsonProperty("movies")]
public IList<Movie> Movies { get; set; }
}
public class Movie
{
IDictionary<string, IList<MovieRating>> MovieInfo { get; set; }
}
public class MovieRating
{
[JsonProperty("rating")]
public string Rating { get; set; }
}
}
It's a bit ugly, but you can do it like this:
class MovieList
{
[JsonProperty("movies")]
public Movie[] Movies { get; set; }
}
class Movie : Dictionary<string, MovieRating[]>
{
}
class MovieRating
{
[JsonProperty("rating")]
public string Rating { get; set; }
}
It's weird that the movie is a dictionary with only one key (its title), but it matches the JSON structure. You can always map it to something more sensible after deserialization.
I consider this a bit of a hack, but it works, and deserializes your json string into the format you required:
class MovieConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Movie);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var movie = new Movie(){
MovieInfo = new Dictionary<string,IList<MovieRating>>()
};
while (reader.Read() && reader.Value != null)
{
var name = (string)reader.Value;
reader.Read();
var ratings = ((JArray)serializer.Deserialize(reader)).Values<string>("rating");
movie.MovieInfo.Add(name, ratings.Select(r => new MovieRating(){ Rating = r}).ToList());
}
return movie;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
I did have to make two changes to your original objects, first I made the accessor for MovieInfo public so I could access it, and second to add the JsonConverterAttribute:
[JsonConverter(typeof(MovieConverter))]
public class Movie
{
public IDictionary<string, IList<MovieRating>> MovieInfo { get; set; }
}

Modify the behaviour of JSON.NET to serialize a collection of objects to an array of IDs

I want to modify JSON.NET so that when I am serializing a Model from my API it sends only an array of IDs for a composite Collection object.
For example:
class Employee
{
public ICollection<Address> Addresses { get; set; }
}
class Address
{
public int id;
public string location;
public string postcode;
}
Then when I send that back through WebApi
Request.Createresponse(HttpStatusCode.OK, new Employee());
Instead of this:
{
"Addresses" :
[
{"id" : 1, "location" : "XX", "postcode" : "XX" },
{"id" : 2, "location" : "XX", "postcode" : "XX" }
]
}
It just sends as this:
{
"Addresss" : [1,2]
}
I want this to be happening application-wide and I don't want to modify at the specific place.
How can I achieve this using the JSON.NET serializer?
You can get the result you want using a custom JsonConverter such as this:
class IdOnlyListConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (typeof(IEnumerable).IsAssignableFrom(objectType) &&
objectType != typeof(string));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JArray array = new JArray();
foreach (object item in (IEnumerable)value)
{
PropertyInfo idProp = item.GetType().GetProperty("id");
if (idProp != null && idProp.CanRead)
{
array.Add(JToken.FromObject(idProp.GetValue(item, null)));
}
}
array.WriteTo(writer);
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
In your model, wherever you have a collection of something where you only want the IDs, decorate the collection property with a [JsonConverter] attribute specifying the custom converter. For example:
class Employee
{
public string name { get; set; }
[JsonConverter(typeof(IdOnlyListConverter))]
public ICollection<Address> Addresses { get; set; }
}
class Address
{
public int id { get; set; }
public string location { get; set; }
public string postcode { get; set; }
}
When the collection gets serialized, the converter will be used, and only the ID values will be written out. Demo:
class Program
{
static void Main(string[] args)
{
Employee emp = new Employee
{
name = "Joe",
Addresses = new List<Address>
{
new Address { id = 1, location = "foo", postcode = "bar" },
new Address { id = 2, location = "baz", postcode = "quux" }
}
};
string json = JsonConvert.SerializeObject(emp);
Console.WriteLine(json);
}
}
Output:
{"name":"Joe","Addresses":[1,2]}
Please have a look at Json.Net's documentation.
public class Address
{
[JsonProperty("Id")]
public int I'd { get; set; }
[JsonIgnore]
public string Location { get; set; }
[JsonIgnore]
public string PostalCode { get; set; }
}
The only drawback to this is that you'll never be able to serialize the Location and PostalCode properties to JSON should you ever want to.
I believe there is a way to specify the serialization that should be used when serializing to JSON with Json.Net. Again, have a look at their documentation.

C#: Deserializing JSON when one field can be different types

I am communicating with an API that returns JSON containing either true, false or an array of string arrays. I wish to deserialize this JSON and store the boolean value, if there is one, in a class field called Success of data type bool, and the array, if there is one, in a field called Result of a custom data type.
What is the best to go about achieving this?
Some JSON:
[
{"status":"ok","result":true},
{
"status":"ok",
"result":
[
{"name":"John Doe","UserIdentifier":"abc","MaxAccounts":"2"}
]
}
]
My Result class:
class Result
{
string Name,
string UserIdentifier,
string MaxAccounts
}
Class for communicating with the Api:
class Api
{
public string Status { get; set; }
public Result[] Result { get; set; }
public bool Success { get; set; }
}
With JSON.NET you could write a custom JSON converter. For example you could have the following objects:
public class Root
{
public string Status { get; set; }
public Result Result { get; set; }
}
public class Result
{
public bool? Value { get; set; }
public Item[] Items { get; set; }
}
public class Item
{
public string Name { get; set; }
public string UserIdentifier { get; set; }
public string MaxAccounts { get; set; }
}
and your JSON will be deserialized to a Root[].
Here's how the custom JSON converter may look like:
public class ResultConverter: JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Result);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Boolean)
{
return new Result
{
Value = (bool)reader.Value,
};
}
return new Result
{
Items = serializer.Deserialize<Item[]>(reader),
};
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// If you want to support serializing you could implement this method as well
throw new NotImplementedException();
}
}
and then:
class Program
{
static void Main()
{
var json =
#"[
{
""status"": ""ok"",
""result"": true
},
{
""status"": ""ok"",
""result"": [
{
""name"": ""John Doe"",
""UserIdentifier"": ""abc"",
""MaxAccounts"": ""2""
}
]
}
]";
var settings = new JsonSerializerSettings();
settings.Converters.Add(new ResultConverter());
Root[] root = JsonConvert.DeserializeObject<Root[]>(json, settings);
// do something with the results here
}
}
create and arraylist using Api and Results objects. I have just tried this and it works.
var api = new Api();
var result = new Result();
api.Status = "ok";
api.Success = true;
result.Name = "John Doe";
result.UserIdentifier = "abc";
result.MaxAccounts = "2";
api.Result = new Result[1];
api.Result[0] = result;
var arrayList = new ArrayList() { new {api.Status, api.Success},
new { api.Status, api.Result} };
var json = JsonConvert.SerializeObject(arrayList, Formatting.Indented);

Categories