How to remove a middle property from JSON in System.Text.Json? - c#

Let's say we have these C# classes:
public class Teacher
{
public long Id { get; set; }
public string Name { get; set; }
public boolean IsActive { get; set; }
public dynamic RelatedItems { get; set; }
}
public class Student
{
public long Id { get; set; }
public string Name { get; set; }
public double AverageScrore { get; set; }
public dynamic RelatedItems { get; set; }
}
public class Course
{
public long Id { get; set; }
public string Title { get; set; }
}
And here's the object graph that is built:
var teacher = teacherService.Get(teacherId);
teacher.RelatedItems.Students = studentService.GetByTeacherId(teacherId);
foreach (var student in teacher.RelatedItems.Students)
{
student.RelatedItems.Courses = courseService.GetStudentCourses(studentId);
}
The object graph above produces this JSON after serialization (using System.Text.Json):
{
"Id": "5",
"Name": "John",
"IsActive": true,
"RelatedItems": {
"Students": [
{
"Id": 7,
"Name": "Joe",
"AverageScore": 9.3,
"RelatedItems": {
"Courses": [
{
"Id": 12,
"Title": "Math"
}
]
}
}
]
}
}
What I need to do is to remove those RelatedItems in the serialized JSON, and move their children one step up. This would be the result:
{
"Id": "5",
"Name": "John",
"IsActive": true,
"Students": [
{
"Id": 7,
"Name": "Joe",
"AverageScore": 9.3,
"Courses": [
{
"Id": 12,
"Title": "Math"
}
]
}
]
}
Is it possible to be done via System.Text.Json?

You have to write custom classes for serialization
var options = new JsonSerializerOptions();
options.Converters.Add(new CustomJsonConverter());
json = JsonSerializer.Serialize(StudentClassObject, options);
Implementation of CustomJsonConverter
public class CustomJsonConverter : JsonConverter<Student>
{
public override Book Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// Use default implementation when deserializing (reading)
return JsonSerializer.Deserialize<Book>(ref reader, options);
}
public override void Write(Utf8JsonWriter writer, Book value, JsonSerializerOptions options)
{
writer.WriteStartObject();
using (JsonDocument document = JsonDocument.Parse(JsonSerializer.Serialize(value)))
{
foreach (var property in document.RootElement.EnumerateObject())
{
if (property.Name != "RelatedItems")
property.WriteTo(writer);
}
}
writer.WriteEndObject();
}
}

Related

Deserialise Enum From JSON String with Spaces

I'll preface this with the fact that I'm trying to avoid using Newtonsoft.Json since, ostensibly, System.Text.Json is ready for prime-time in .NET 6.
So I have two enums coming from an API and I want to deserialise them using this test method:
[Theory]
[ClassData(typeof(TestDataGenerator))]
public void CanDeserialiseEnumsWithCustomJsonStrings(Enum expected, string jsonName)
{
jsonName.ShouldNotBeNullOrEmpty();
ReadOnlySpan<char> json = $"{{\"TestEnum\":\"{jsonName}\"}}";
Type constructed = typeof(TestEnumWrapper<>).MakeGenericType(expected.GetType());
var res = JsonSerializer.Deserialize(json, constructed);
constructed.GetProperty("TestEnum").GetValue(res).ShouldBe(expected);
}
private class TestEnumWrapper<T> where T: struct
{
public T TestEnum { get; set; }
}
(Yes I know that this could be done with JsonSerializer.Deserialize<T>(), I want to be able to test many types with this test so I need the reflection AFAICT).
The first one, works fine:
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum RecordType
{
[JsonPropertyName("country")]
Country = 1,
[JsonPropertyName("destinationOrbit")]
DestinationOrbit = 2,
[JsonPropertyName("engine")]
Engine = 3,
//etc...
}
The second one, fails on the deserialization, this seems to be due to the spaces in the names.
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum ObjectClass
{
[JsonPropertyName("Rocket Body")]
RocketBody,
[JsonPropertyName("Rocket Debris")]
RocketDebris,
[JsonPropertyName("Rocket Fragmentation Debris")]
RocketFragmentationDebris,
[JsonPropertyName("Rocket Mission Related Object")]
RocketMissionRelatedObject,
//etc...
}
The API is controlled by the European Space Agency, so somehow, I don't think I'll be able to persuade them to rationalise the response a bit more.
Is there any way around this?
Some have asked for an example of the JSON I'm trying to deserialise. I'm currently working on the Attributes part of this blob:
{
"type": "object",
"attributes": {
"shape": null,
"xSectMin": null,
"satno": null,
"depth": null,
"objectClass": "Rocket Fragmentation Debris",
"cosparId": null,
"length": null,
"height": null,
"mass": null,
"xSectMax": null,
"vimpelId": 84303,
"xSectAvg": null,
"name": null
},
"relationships": {
"states": {
"links": {
"self": "/api/objects/61345/relationships/states",
"related": "/api/objects/61345/states"
}
},
"initialOrbits": {
"links": {
"self": "/api/objects/61345/relationships/initial-orbits",
"related": "/api/objects/61345/initial-orbits"
}
},
"destinationOrbits": {
"links": {
"self": "/api/objects/61345/relationships/destination-orbits",
"related": "/api/objects/61345/destination-orbits"
}
},
"operators": {
"links": {
"self": "/api/objects/61345/relationships/operators",
"related": "/api/objects/61345/operators"
}
},
"launch": {
"links": {
"self": "/api/objects/61345/relationships/launch",
"related": "/api/objects/61345/launch"
}
},
"reentry": {
"links": {
"self": "/api/objects/61345/relationships/reentry",
"related": "/api/objects/61345/reentry"
}
}
},
"id": "61345",
"links": {
"self": "/api/objects/61345"
}
}
This is a solution for System.Text.Json
I used the Nuget package System.Text.Json.EnumExtensions
https://github.com/StefH/System.Text.Json.EnumExtensions
// you probably want these options somewhere global
private JsonSerializerOptions options;
private class TestEnumWrapper<T> where T : struct
{
public T TestEnum { get; set; }
}
public enum ObjectClass
{
[EnumMember(Value = "Rocket Body")]
RocketBody,
[EnumMember(Value = "Rocket Debris")]
RocketDebris,
[EnumMember(Value = "Rocket Fragmentation Debris")]
RocketFragmentationDebris,
[EnumMember(Value = "Rocket Mission Related Object")]
RocketMissionRelatedObject,
//etc...
}
private void CanDeserialiseEnumsWithCustomJsonStrings(Enum expected, string jsonName)
{
var json = $"{{\"TestEnum\":\"{jsonName}\"}}";
Type constructed = typeof(TestEnumWrapper<>).MakeGenericType(expected.GetType());
var res = JsonSerializer.Deserialize(json, constructed, options);
}
public void Test()
{
this.options = new JsonSerializerOptions();
options.Converters.Add(new JsonStringEnumConverterWithAttributeSupport());
var testSerialize = JsonSerializer.Serialize(new TestEnumWrapper<ObjectClass>()
{ TestEnum = ObjectClass.RocketBody }, options);
// Test Deserialize
CanDeserialiseEnumsWithCustomJsonStrings(ObjectClass.RocketBody, "Rocket Body");
}
You only have to add the new JsonStringEnumConverterWithAttributeSupport() to the JsonSerializerOptions.Converters for this to work.
If you want to use the Converter as an Attribute you have to add an parameterless constructor to the class JsonStringEnumConverterWithAttributeSupport
public JsonStringEnumConverterWithAttributeSupport() : this(namingPolicy : null, allowIntegerValues : true,
parseEnumMemberAttribute : true, parseDisplayAttribute : false, parseDescriptionAttribute : false)
{
}
try this, I uses Newtonsoft.Json and for test I deserialized only attributes since only they contain enum. You don't need any custom code.
var attributes= JsonConvert.DeserializeObject<Root>(json);
classes
public enum ObjectClass
{
[EnumMember(Value = "Rocket Body")]
RocketBody,
[EnumMember(Value ="Rocket Debris")]
RocketDebris,
[EnumMember(Value = "Rocket Fragmentation Debris")]
RocketFragmentationDebris,
[EnumMember(Value ="Rocket Mission Related Object")]
RocketMissionRelatedObject
}
public partial class Root
{
[JsonProperty("attributes")]
public Attributes Attributes { get; set; }
}
public partial class Attributes
{
[JsonProperty("shape")]
public object Shape { get; set; }
[JsonProperty("xSectMin")]
public object XSectMin { get; set; }
[JsonProperty("satno")]
public object Satno { get; set; }
[JsonProperty("depth")]
public object Depth { get; set; }
[JsonProperty("objectClass")]
public ObjectClass ObjectClass { get; set; }
[JsonProperty("cosparId")]
public object CosparId { get; set; }
[JsonProperty("length")]
public object Length { get; set; }
[JsonProperty("height")]
public object Height { get; set; }
[JsonProperty("mass")]
public object Mass { get; set; }
[JsonProperty("xSectMax")]
public object XSectMax { get; set; }
[JsonProperty("vimpelId")]
public long VimpelId { get; set; }
[JsonProperty("xSectAvg")]
public object XSectAvg { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
}
test
json=JsonConvert.SerializeObject(attributes);
attributes= JsonConvert.DeserializeObject<Root>(json);
result
{
"attributes": {
"shape": null,
"xSectMin": null,
"satno": null,
"depth": null,
"objectClass": 2,
"cosparId": null,
"length": null,
"height": null,
"mass": null,
"xSectMax": null,
"vimpelId": 84303,
"xSectAvg": null,
"name": null
}
}

Complicated Json to C# Object Deserialize with classes

I am trying to deserialize a json response I get from a web call. I have it 90 percent figured out. The only part I am having a hard time figuring out is there are these json arrays which have data in them and each array name is unique using the email address. I have not been able to figure out how to turn the Email Arrays into 1. Dynamic and having it create many lists or just a couple depending on what comes back in the response and also dynamically naming the list arrays to put the data into the Records class.
As you can see in the Records class I need this to be more dynamic and flexible to receive any and all emails.
Below is the json:
{
"result": {
"records": {
"joeblow#gmail.com": [
{
"OrderId": "d9535109-d305-4584-a503-8194bbcfcff2",
"CompletedOrderId": "BCFCFF2",
"CustomerId": 1212,
"CompletedTime": "2020-10-26 13:32:02",
"Email": "joeblow#gmail.com",
"ShippingFirstName": "Joe",
"ShippingMiddleName": "",
"ShippingLastName": "Blow",
"LineItems": {
"tfl.es.bluray": { "qty": 1 },
"booklets.en.ebook": { "qty": 1 }
}
}
],
"cleob#hotmail.com": [
{
"OrderId": "7bf97b3a-bc46-411c-bc30-12563326dba0",
"CompletedOrderId": "326DBA0",
"CustomerId": 1212,
"CompletedTime": "2020-10-26 20:07:44",
"Email": "cleob#hotmail.com",
"ShippingFirstName": "Cleo",
"ShippingMiddleName": "",
"ShippingLastName": "Blue",
"LineItems": {
"tfl.es.bluray": { "qty": 1 },
"booklets.en.ebook": { "qty": 1 },
"aos.en.pb": { "qty": 1 },
"course-tos.en.olr": { "qty": 1 },
"pow-hsk-nofilm.en.combo": { "qty": 1 },
"course-organizing.en.olr": { "qty": 1 }
}
}
],
"johnd#gmail.com": [
{
"OrderId": "630f0dda-94c3-4b82-a070-2554004dce29",
"CompletedOrderId": "04DCE29",
"CustomerId": 12345,
"CompletedTime": "2020-10-25 21:52:04",
"Email": "johnd#gmail.com",
"ShippingFirstName": "John",
"ShippingMiddleName": "",
"ShippingLastName": "Doe",
"LineItems": {
"tfl.es.bluray": { "qty": 1 },
"booklets.en.ebook": { "qty": 1 },
"aos.en.pb": { "qty": 1 },
"course-tos.en.olr": { "qty": 1 },
"pow-hsk-nofilm.en.combo": { "qty": 1 },
"course-organizing.en.olr": { "qty": 1 },
"oak-2007.en.cd": { "qty": 1 }
}
}
]
},
"errors": [
{
"id": "bademailaddress-yahoo.com",
"message": "Email address 'bademailaddress-yahoo.com' is not a valid email address"
}
]
},
"jsonrpc": "2.0",
"id": 12345634523
}
And the classes I made for the json deserialization:
public partial class JsonEmailDeSerializer
{
[JsonProperty("result")]
public Result Result { get; set; }
[JsonProperty("jsonrpc")]
public string Jsonrpc { get; set; }
[JsonProperty("id")]
public long Id { get; set; }
}
public partial class Result
{
[JsonProperty("records")]
public Records Records { get; set; }
[JsonProperty("errors")]
public List<Error> Errors { get; set; }
}
public partial class Error
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("message")]
public string Message { get; set; }
}
public partial class Records
{
[JsonProperty("joeblow#gmail.com")]
public List<MailCom> JoeblowGmailCom { get; set; }
[JsonProperty("cleob#hotmail.com")]
public List<MailCom> CleobHotmailCom { get; set; }
[JsonProperty("johnd#gmail.com")]
public List<MailCom> JohndGmailCom { get; set; }
}
public partial class MailCom
{
[JsonProperty("OrderId")]
public Guid OrderId { get; set; }
[JsonProperty("CompletedOrderId")]
public string CompletedOrderId { get; set; }
[JsonProperty("CustomerId")]
public long CustomerId { get; set; }
[JsonProperty("CompletedTime")]
public DateTimeOffset CompletedTime { get; set; }
[JsonProperty("Email")]
public string Email { get; set; }
[JsonProperty("ShippingFirstName")]
public string ShippingFirstName { get; set; }
[JsonProperty("ShippingMiddleName")]
public string ShippingMiddleName { get; set; }
[JsonProperty("ShippingLastName")]
public string ShippingLastName { get; set; }
[JsonProperty("LineItems")]
public Dictionary<string, LineItem> LineItems { get; set; }
}
public partial class LineItem
{
[JsonProperty("qty")]
public long Qty { get; set; }
}
public partial class JsonEmailDeSerializer
{
public static JsonEmailDeSerializer FromJson(string json) => JsonConvert.DeserializeObject<JsonEmailDeSerializer>(json, FedExShipper.Converter.Settings);
}
public static class Serialize
{
public static string ToJson(this JsonEmailDeSerializer self) => JsonConvert.SerializeObject(self, FedExShipper.Converter.Settings);
}
internal static class Converter
{
public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
{
MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
DateParseHandling = DateParseHandling.None,
Converters =
{
new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }
},
};
}
Common approach to deserializing json with dynamic property names is to use Dictionary<string, ...>, in this case - Dictionary<string, List<MailCom>> can be used for Records property:
public partial class Result
{
[JsonProperty("records")]
public Dictionary<string, List<MailCom>> Records { get; set; }
[JsonProperty("errors")]
public List<Error> Errors { get; set; }
}
Json.NET can treat json object properties as keys for dictionary which makes it suitable to deserialize such dynamic data. The same is true for System.Text.Json.
That will never serialize to a class properly.
You'll have to use a lower-level API like Utf8JsonReader to read that level in the document, at least.

Deserialize a json object with different structure and same name

I write an app that gets IMDb movie information by scraping movie page source. Some of the movie data in page source are in JSON format with movie schema from "Schema.org".
{
"#context": "http://schema.org",
"#type": "Movie",
"url": "/title/tt7131622/",
"name": "Once Upon a Time... in Hollywood",
"genre": [
"Comedy",
"Drama"
],
"actor": [
{
"#type": "Person",
"url": "/name/nm0000138/",
"name": "Leonardo DiCaprio"
},
{
"#type": "Person",
"url": "/name/nm0000093/",
"name": "Brad Pitt"
},
{
"#type": "Person",
"url": "/name/nm3053338/",
"name": "Margot Robbie"
},
{
"#type": "Person",
"url": "/name/nm0386472/",
"name": "Emile Hirsch"
}
],
"director": {
"#type": "Person",
"url": "/name/nm0000233/",
"name": "Quentin Tarantino"
},
"creator": [
{
"#type": "Person",
"url": "/name/nm0000233/",
"name": "Quentin Tarantino"
},
{
"#type": "Organization",
"url": "/company/co0050868/"
},
{
"#type": "Organization",
"url": "/company/co0452101/"
},
{
"#type": "Organization",
"url": "/company/co0159772/"
}
}
I made a "Movie" class to deserialize the JSON object. There is a property Person class with the name "Director".
internal class ImdbJsonMovie
{
public string Url { get; set; }
public string Name { get; set; }
public string Image { get; set; }
public List<string> Genre { get; set; }
public List<ImdbJsonPerson> Actor { get; set; }
public ImdbJsonPerson Director { get; set; }
//public string[] Creator { get; set; }
}
It's OK. But the problem is some movies such as "The Matrix" have more than one director.
{
"#context": "http://schema.org",
"#type": "Movie",
"url": "/title/tt0133093/",
"name": "The Matrix",
"genre": [
"Action",
"Sci-Fi"
],
"actor": [
{
"#type": "Person",
"url": "/name/nm0000206/",
"name": "Keanu Reeves"
},
{
"#type": "Person",
"url": "/name/nm0000401/",
"name": "Laurence Fishburne"
},
{
"#type": "Person",
"url": "/name/nm0005251/",
"name": "Carrie-Anne Moss"
},
{
"#type": "Person",
"url": "/name/nm0915989/",
"name": "Hugo Weaving"
}
],
"director": [
{
"#type": "Person",
"url": "/name/nm0905154/",
"name": "Lana Wachowski"
},
{
"#type": "Person",
"url": "/name/nm0905152/",
"name": "Lilly Wachowski"
}
],
"creator": [
{
"#type": "Person",
"url": "/name/nm0905152/",
"name": "Lilly Wachowski"
},
{
"#type": "Person",
"url": "/name/nm0905154/",
"name": "Lana Wachowski"
},
{
"#type": "Organization",
"url": "/company/co0002663/"
},
{
"#type": "Organization",
"url": "/company/co0108864/"
},
{
"#type": "Organization",
"url": "/company/co0060075/"
},
{
"#type": "Organization",
"url": "/company/co0019968/"
},
{
"#type": "Organization",
"url": "/company/co0070636/"
}
}
So it must be List<Person>.
internal class ImdbJsonMovie
{
public string Url { get; set; }
public string Name { get; set; }
public string Image { get; set; }
public List<string> Genre { get; set; }
public List<ImdbJsonPerson> Actor { get; set; }
public List<ImdbJsonPerson> Director { get; set; }
//public string[] Creator { get; set; }
}
Another problem is how to deserialize creator property that is made by the Person class and Organization class.
So the question is "How to deserialize this complex JSON object?"
Thank you
Did you try: https://app.quicktype.io/?l=csharp? It can generate model in C# for you, which is very good begining for further changes (if the Schema has to be different according to different json responses)
I did enter your JSON and the model created is following:
namespace QuickType
{
using System;
using System.Collections.Generic;
using System.Globalization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
public partial class Movies
{
[JsonProperty("#context")]
public Uri Context { get; set; }
[JsonProperty("#type")]
public string Type { get; set; }
[JsonProperty("url")]
public string Url { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("genre")]
public List<string> Genre { get; set; }
[JsonProperty("actor")]
public List<Tor> Actor { get; set; }
[JsonProperty("director")]
public List<Tor> Director { get; set; }
[JsonProperty("creator")]
public List<Tor> Creator { get; set; }
}
public partial class Tor
{
[JsonProperty("#type")]
public TypeEnum Type { get; set; }
[JsonProperty("url")]
public string Url { get; set; }
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; set; }
}
public enum TypeEnum { Organization, Person };
internal static class Converter
{
public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
{
MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
DateParseHandling = DateParseHandling.None,
Converters =
{
TypeEnumConverter.Singleton,
new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }
},
};
}
internal class TypeEnumConverter : JsonConverter
{
public override bool CanConvert(Type t) => t == typeof(TypeEnum) || t == typeof(TypeEnum?);
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);
switch (value)
{
case "Organization":
return TypeEnum.Organization;
case "Person":
return TypeEnum.Person;
}
throw new Exception("Cannot unmarshal type TypeEnum");
}
public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
{
if (untypedValue == null)
{
serializer.Serialize(writer, null);
return;
}
var value = (TypeEnum)untypedValue;
switch (value)
{
case TypeEnum.Organization:
serializer.Serialize(writer, "Organization");
return;
case TypeEnum.Person:
serializer.Serialize(writer, "Person");
return;
}
throw new Exception("Cannot marshal type TypeEnum");
}
public static readonly TypeEnumConverter Singleton = new TypeEnumConverter();
}
}
[Update]
As for problems with sometime single, sometime array thing --> look here: How to handle both a single item and an array for the same property using JSON.net
Thank you #Piotr. It completely worked. because your first part of the answer was not correct for me, I rewrite your response as an answer.
as you said the correct answer was in this link.
https://stackoverflow.com/questions/18994685/how-to-handle-both-a-single-item-and-an-array-for-the-same-property-using-json-n
So I made this class.
class JsonConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(List<T>);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
return token.ToObject<List<T>>();
}
return new List<T> { token.ToObject<T>() };
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
and changed my Movie Class to this.
internal class ImdbJsonMovie
{
public string Url { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("image")]
public string Image { get; set; }
[JsonProperty("genre")]
[JsonConverter(typeof(JsonConverter<string>))]
public List<string> Genre { get; set; }
[JsonProperty("contentRating")]
public string ContentRating { get; set; }
[JsonProperty("actor")]
[JsonConverter(typeof(JsonConverter<ImdbJsonTypeEnum>))]
public List<ImdbJsonTypeEnum> Actor { get; set; }
[JsonProperty("director")]
[JsonConverter(typeof(JsonConverter<ImdbJsonTypeEnum>))]
public List<ImdbJsonTypeEnum> Director { get; set; }
[JsonProperty("creator")]
[JsonConverter(typeof(JsonConverter<ImdbJsonTypeEnum>))]
public List<ImdbJsonTypeEnum> Creator { get; set; }
}
and this Enum
public class ImdbJsonTypeEnum
{
[JsonProperty("#type")]
public TypeEnum Type { get; set; }
[JsonProperty("url")]
public string Url { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
public enum TypeEnum
{
Organization,
Person
};
}
It worked for one director and multi director movies.
Thank you

Serialize a Dictionary with an object type key and a List type value into Json

I'd like to convert into Json the following Dictionary type:
public class Repsonse
{
public Dictionary<Employee, List<Car>> Dictionary { get; set; }
public class Employee
{
public string Name { get; set; }
public string Id { get; set; }
public decimal Seniority { get; set; }
}
public class Car
{
public string OwnerId { get; set; }
public string Model { get; set; }
public string RegistrationPlate { get; set; }
}
}
I expect it to be serialized into the following Json:
{
"Dictionary": [
{
"Name": "John Doe",
"Seniority": "2",
"OwnerId": "1111"
"Cars": [
{
"OwnerId": "1111"
"Model": "Chevrolet Spark",
"RegistrationPlate": "LTO1234"
},
{
"OwnerId": "1111"
"Model": "Chevrolet Malibu",
"RegistrationPlate": "LTO5678"
}
]
},
{
"Name": "Jane Doe",
"Seniority": "10",
"OwnerId": "9999"
"Cars": [
{
"OwnerId": "9999"
"Model": "Mercedes Benz",
"RegistrationPlate": "ABC1234"
},
{
"OwnerId": "9999"
"Model": "Mercedes Maybach",
"RegistrationPlate": "ABC5678"
}
]
}
]
}
I'm using NewtonSoft Json for the serialization, and read that I need to use a TypeConverter to enable this kind of serialization, but it doesn't work for me.
Here's the TypeConverter implemenetation:
public class Employee : TypeConverter
{
public string Name { get; set; }
public string Id { get; set; }
public decimal Seniority { get; set; }
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string))
{
return ((Employee)value).Name + "," + ((Employee)value).Id + "," + ((Employee)value).Seniority;
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
You can see the full example code here.
How can I make it work?
The json output format that you have is for list not for dictionary. If you want to use dictionary may be you can follow below one
JSON Response
{
"Dictionary": {
"John Doe": {
"Name": "John Doe",
"Id": "1111",
"Seniority": 2,
"Cars": [
{
"OwnerId": "1111",
"Model": "Chevrolet Spark",
"RegistrationPlate": "LTO1234"
},
{
"OwnerId": "1111",
"Model": "Chevrolet Malibu",
"RegistrationPlate": "LTO5678"
}
]
},
"Jane Doe": {
"Name": "Jane Doe",
"Id": "9999",
"Seniority": 10,
"Cars": [
{
"OwnerId": "9999",
"Model": "Mercedes Benz",
"RegistrationPlate": "ABC1234"
},
{
"OwnerId": "9999",
"Model": "Mercedes Maybach",
"RegistrationPlate": "ABC5678"
}
]
}
}
}
and code will be
public class Program
{
public static void Main()
{
Console.WriteLine("Hello World");
Response res = new Response { Dictionary = new Dictionary<string, Employee>() };
Employee firstEmployee = new Employee { Name = "John Doe", Id = "1111", Seniority = 2 };
Employee secondEmployee = new Employee { Name = "Jane Doe", Id = "9999", Seniority = 10 };
firstEmployee.Cars.Add(new Car { OwnerId = "1111", Model = "Chevrolet Spark", RegistrationPlate = "LTO1234" });
firstEmployee.Cars.Add(new Car { OwnerId = "1111", Model = "Chevrolet Malibu", RegistrationPlate = "LTO5678" });
secondEmployee.Cars.Add(new Car { OwnerId = "9999", Model = "Mercedes Benz", RegistrationPlate = "ABC1234" });
secondEmployee.Cars.Add(new Car { OwnerId = "9999", Model = "Mercedes Maybach", RegistrationPlate = "ABC5678" });
res.Dictionary.Add(firstEmployee.Name, firstEmployee);
res.Dictionary.Add(secondEmployee.Name, secondEmployee);
var result = JsonConvert.SerializeObject(res);
Console.WriteLine(result);
}
}
public class Response
{
public Dictionary<string, Employee> Dictionary { get; set; }
public class Employee : TypeConverter
{
public Employee(string name, decimal seniority, string id, List<Car> cars)
{
Name = name;
Seniority = seniority;
Id = id;
Cars = cars;
Cars = new List<Car>();
}
public Employee()
{
Cars = new List<Car>();
}
public string Name { get; set; }
public string Id { get; set; }
public decimal Seniority { get; set; }
public List<Car> Cars { get; set; }
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string))
{
return ((Employee)value).Name + "," + ((Employee)value).Id + "," + ((Employee)value).Seniority;
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
public class Car
{
public string OwnerId { get; set; }
public string Model { get; set; }
public string RegistrationPlate { get; set; }
}
}

Deserializing JSON containing single or array of objects [duplicate]

This question already has answers here:
How to handle both a single item and an array for the same property using JSON.net
(9 answers)
Closed 5 years ago.
I'm trying to deserialize event data that includes image urls.
When the event includes a single image, the format of the JSON string is:
"images": {
"image": {
"small": {...},
"width": "48",
"creator": null,
"thumb": {...},
"height": "48",
"url": "http://example.com/image1.jpeg",
"medium": {...},
"id": "1"
}
}
When multiple images are available, the format of the response changes to:
"images": {
"image": [
{
"small": {...},
"width": "48",
"creator": null,
"thumb": {...},
"height": "48",
"url": "http://example.com/image1.jpeg",
"medium": {...},
"id": "1"
},
{
"small": {...},
"width": "48",
"creator": null,
"thumb": {...},
"height": "48",
"url": "http://example.com/image2.jpeg",
"medium": {...},
"id": "2"
}
]
}
When I'm trying to deserialize, I can get one or other model to work, but not both at the same time.
My model is something like:
public class Event
{
public string id { get; set; }
public string imageId { get; set; }
public Image image { get; set; }
public Images images { get; set; }
}
public class Image
{
public string id { get; set; }
public string width { get; set; }
public string caption { get; set; }
public string url { get; set; }
public string height { get; set; }
public int smallId { get; set; }
public Small small { get; set; }
public int mediumId { get; set; }
public Medium medium { get; set; }
public int thumbId { get; set; }
public Thumb thumb { get; set; }
}
If I define my Images class as follows, it works with arrays:
public class Images
{
public string id { get; set; }
public List<Image> image { get; set; }
}
If I define it as follows, it works with single images:
public class Images
{
public string id { get; set; }
public Image image { get; set; }
}
In theory, I could contain both a List and an Image in the Images class, but I can't use the same name for both and the deserialization doesn't work if I change either name.
I understand that both responses are valid JSON (they represent what they're supposed to), but is this a bad implementation?
Or am I missing something in my model/deserialization that should allow me to deserialize both the single object and array?
Thanks.
UPDATE
Deserialization code:
var feedUrl = url;
var client = new HttpClient();
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
var serializer = new DataContractJsonSerializer(typeof(EventDetail));
var streamTask = client.GetStreamAsync(feedUrl);
EventDetail eventDetail = serializer.ReadObject(await streamTask) as EventDetail;
return eventDetail;
Similar question was asked in the past: "How to handle both a single item and an array for the same property using JSON.net". The approach is basically to use customConverter as follow:
public class Images
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("image")]
[JsonConverter(typeof(SingleOrArrayConverter<Image>))]
public List<Image> Image { get; set; }
}
public class SingleOrArrayConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(List<T>));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
return token.ToObject<List<T>>();
}
return new List<T> { token.ToObject<T>() };
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Without seeing your serialization/deserialization code I think the problem is there.
One other Solution could be that you send an empty list of Images (Images) if you only have one Image and the other way around. So you always have Imageand Images in your JSON.

Categories

Resources