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
}
}
Related
I am getting the following response from an API
"results": {
"wan1": {
"id": "wan1",
"name": "wan1",
"alias": "",
"mac": "00:00:00:00:00:00",
"ip": "102.165.223.199",
"mask": 24,
"link": true,
"speed": 1000.0,
"duplex": 1,
"tx_packets": 501850173,
"rx_packets": 307154377,
"tx_bytes": 442319826490,
"rx_bytes": 234140958061,
"tx_errors": 0,
"rx_errors": 0
},
"dmz": {
"id": "dmz",
"name": "dmz",
"alias": "",
"mac": "00:00:00:00:00:00",
"ip": "10.10.10.1",
"mask": 24,
"link": false,
"speed": 0.0,
"duplex": 0,
"tx_packets": 0,
"rx_packets": 0,
"tx_bytes": 0,
"rx_bytes": 0,
"tx_errors": 0,
"rx_errors": 0
},
"internal1": {
"id": "internal1",
"name": "internal1",
"alias": "",
"mac": "00:00:00:00:00:00",
"ip": "0.0.0.0",
"mask": 0,
"link": false,
"speed": 0.0,
"duplex": 0,
"tx_packets": 0,
"rx_packets": 0,
"tx_bytes": 0,
"rx_bytes": 0,
"tx_errors": 0,
"rx_errors": 0
}
},
"vdom": "root",
}
I have tried a few approaches to representing this JSON in c# objects
{
public Dictionary<string, List<Result>> Result { get; set; }
}
public class Result
{
public string name { get; set; }
public string ip { get; set; }
public int tx_bytes { get; set; }
public int rx_bytes { get; set; }
}
And here is the method I am using to deserialize the JSON:
var result = await client.Request()
.AppendPathSegment("api/v2/monitor/system/interface")
.SetQueryParam("access_token", token)
.GetJsonAsync<InterfaceResponse>(cancellationToken: cancellationToken);
It should be simple, but for some reason, I can't figure out the correct object representation, but when I debug I am getting null
Thanks for the help.
I can see 2 issues:
It's "results" not "result".
"results" looks like Dictionary<string, Result> not Dictionary<string, List<Result>>.
Additionally, if you're using System.Text.Json then casing may matter depending on your settings.
try this
var jsonParsed = JObject.Parse(json);
Dictionary<string,Result> results = jsonParsed["results"]
.ToObject<Dictionary<string,Result>>();
string vdom = (string)jsonParsed["vdom"];
public class Result
{
public string name { get; set; }
public string ip { get; set; }
public long tx_bytes { get; set; }
public long rx_bytes { get; set; }
//another properties
}
You need to fix the classes:
public class InterfaceResponse
{
// 1. rename or use attributes
// 2. fix type from Dictionary<string, List<Result>>
public Dictionary<string, Result> results { get; set; }
}
public class Result
{
public string name { get; set; }
public string ip { get; set; }
public long tx_bytes { get; set; } // use long, some of the values are too big to fit int
public long rx_bytes { get; set; } // use long, some of the values are too big to fit int
}
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();
}
}
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.
I'm struggling to make this conversion happen, and not sure it's entirely feasible. My JSON from a third party could look like this:
{
"equipments": [
{
"serialNumber": "11-17-053",
"equipmentType_id": "589dda4952172110008870c7",
"created": 1508856453875,
"fieldOffice_id": "594af5425fbfca00111a0c20",
"clients_id": [],
"notes": "",
"isInService": true,
"metavalues": {
"0t78nzhp9w265avlvt": {
"item_ids": [
33121
]
},
"7ogz4kehqh8h3cwip8": {
"item_ids": [
33128
]
}
},
"schedules": [],
"id": "59ef52854d40a9009c787596"
},
{
"serialNumber": "11-17-054",
"equipmentType_id": "589dda4952172110008870c7",
"created": 1508856453875,
"fieldOffice_id": "594af5425fbfca00111a0c20",
"clients_id": [],
"notes": "",
"isInService": true,
"metavalues": {
"0t78nzhp9w265avlvt": {
"item_ids": [
33121
]
},
"7ogz4kehqh8h3cwip8": {
"item_ids": [
33128
]
}
},
"schedules": [],
"id": "59ef52854d40a9009c787597"
},
{
"serialNumber": "8-17-022",
"equipmentType_id": "589dda4952172110008870c7",
"created": 1505326964589,
"fieldOffice_id": "594af5425fbfca00111a0c20",
"clients_id": [],
"notes": "",
"isInService": true,
"metavalues": {
"0t78nzhp9w265avlvt": {
"item_ids": [
33121
]
},
"7ogz4kehqh8h3cwip8": {
"item_ids": [
33128
]
}
},
"schedules": [],
"id": "59b9777480e426009d01d48d"
},
{
"serialNumber": "22A-17-001",
"equipmentType_id": "589dda4952172110008870c7",
"created": 1504908025733,
"fieldOffice_id": "58b74b080c206710004ff726",
"clients_id": [
"59bbfdf5725cd00012fb15d8"
],
"notes": "",
"isInService": true,
"metavalues": {
"0t78nzhp9w265avlvt": {
"item_ids": [
33122
]
},
"7ogz4kehqh8h3cwip8": {
"item_ids": [
33128
]
},
"g99idmcqyuo2na9e6l": "YARD",
"709pm94prt2tpjt90y": 5,
"bgen1h4i5b6f8xa1kh": "9/8/2017 7:18:24 PM",
"yvtvsl8dedudqjtdud": "0.00000, 0.00000",
"aj3h2b5fdbic9s72m3": "Parex",
"8wt1re82xidjiv8rzi": "YARD"
},
"schedules": [],
"id": "59b312f93256d5009c4a73fb"
},....
This is obviously not a complete example, but should help get my question across.
Is there a way to create a C# class that pulls in certain fields only if they exist from the "metavalues" array?
My current class is as follows which works to get the data, but not exactly how I want.
public class Equipment
{
[JsonProperty("serialNumber")]
public string SerialNumber { get; set; }
public string equipmentType_id { get; set; }
public bool isInService { get; set; }
public List<string> clients_Id { get; set; }
public string fieldOffice_id { get; set; }
[JsonProperty("id")]
public string EquipmentId { get; set; }
[JsonProperty("metavalues")]
public Dictionary<string, object> metavalues { get; set; }
}
What I'm after, is taking the key of "g99idmcqyuo2na9e6l" which is optional in the metavalues, and store it in a property called "LeaseName".
I tried the following to no avail.
public class Equipment
{
[JsonProperty("serialNumber")]
public string SerialNumber { get; set; }
public string equipmentType_id { get; set; }
public bool isInService { get; set; }
public List<string> clients_Id { get; set; }
public string fieldOffice_id { get; set; }
[JsonProperty("id")]
public string EquipmentId { get; set; }
[JsonProperty("g99idmcqyuo2na9e6l")]
public string LeaseName { get; set; }
}
If I try to make a class for the metavalues section, I get an exception indicating that JSON.NET can't convert it to my object, hence why I used the Dictionary<string, object> option.
EDIT # 1: The accepted answer works for me, but for those who stumble upon this and truly need a property name from a nested array you could try the following.
[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
if (metavalues != null)
{
if (metavalues.ContainsKey("g99idmcqyuo2na9e6l"))
{
string _LeaseName = (string)metavalues["g99idmcqyuo2na9e6l"];
LeaseName = _LeaseName;
}
}
}
I think my edit approach is a bit overkill but just throwing this out there.
Yes, this is possible, but since the lease value is still one level down in the JSON you need an intermediate class to hold it (to replace the dictionary).
public class Equipment
{
...
[JsonProperty("metavalues")]
public MetaValues MetaValues { get; set; }
}
public class MetaValues
{
[JsonProperty("g99idmcqyuo2na9e6l")]
public string LeaseName { get; set; }
}
Fiddle: https://dotnetfiddle.net/Ddqzc7
I am using System.Net.Http.HttpClient, the version currently available in NuGet,
to retrieve data from a service in json format. The data roughly looks like this:
{
"schema": "Listing",
"data": {
"key": "28ba648c-de24-45d4-a7d9-70f810cf5438",
"children": [{
"kind": "type1",
"data": {
"body": "Four score and seven years ago...",
"parent_id": "2qh3l",
"report_count": 0,
"name": "c4j6yeh"
}
}, {
"kind": "type3",
"data": {
"domain": "abc.def.com",
"flagged": true,
"category": "news",
"saved": false,
"id": "t3dz0",
"created": 1335998011.0
}
}]
}
}
I use HttpContentExtensions.ReadAsAsync<T> to de-serialize that json string into an object graph. The type definitions looks roughly like this:
public class Response
{
public String schema { get;set; }
public ListingData data { get;set; }
}
public class ListingData
{
public string key { get;set; }
public List<OneItem> children { get;set; }
}
Here's the problem: I desire the type of the items in children to vary depending on the kind property. If kind is "type1" then I want to de-serialize an object of... let's call it Type1 . If kind is "type3" then I want an object of type Type3.
Right now, I can deserialize a List<Type1> or a List<Type3>, but I don't know how to tell the de-serialization logic to distinguish between the two.
I could merge all the properties of the "type1" data object and the "type3" data object into a single .NET Type. But the number of properties is large enough that this gets messy.
If the name of the property in the JSON (in this case data) were different, I could distinguish using that. If, for example, the data looked like this:
"children": [{
"kind": "type1",
"t1data": { ... }
}, {
"kind": "type3",
"t3data": { ... }
}]
...then I could do something like this in .NET:
public class OneItem
{
public string kind { get;set; }
public Type1 t1data { get;set; }
public Type3 t3data { get;set; }
}
But my data schema doesn't look like that.
Is it possible to choose the type for de-serialization by the content of the data? In other words,
look at the value of one property (in this case, kind) to determine how to de-serialize the content for another property (in this case, data).
Or is it possible to inject a filter or transformer that acts on the JSON before ReadAsAsync tries to deserialize it?
If so, How?
If you're ok w/ doing some pre-processing on your response and you can use Json.NET, you should be able to do what you want.
Given the following classes:
public class Response
{
public string schema
{
get;
set;
}
public ListingData data
{
get;
set;
}
}
public class ListingData
{
public string key
{
get;
set;
}
public List<object> children
{
get;
set;
}
}
public class Type1
{
public string body
{
get;
set;
}
public string parent_id
{
get;
set;
}
public int report_count
{
get;
set;
}
public string name
{
get;
set;
}
}
public class Type3
{
public string domain
{
get;
set;
}
public bool flagged
{
get;
set;
}
public string category
{
get;
set;
}
public bool saved
{
get;
set;
}
public string id
{
get;
set;
}
public double created
{
get;
set;
}
}
This test passes:
[Test]
public void RoundTrip()
{
var response = new Response
{
schema = "Listing",
data = new ListingData
{
key = "28ba648c-de24-45d4-a7d9-70f810cf5438",
children = new List<object>
{
new Type1
{
body = "Four score and seven years ago...",
parent_id = "2qh3l",
report_count = 0,
name = "c4j6yeh"
},
new Type3
{
domain = "abc.def.com",
flagged = true,
category = "news",
saved = false,
id = "t3dz0",
created = 1335998011.0
}
}
}
};
var jsonSerializerSettings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
TypeNameHandling = TypeNameHandling.Objects
};
string serializedResponse = JsonConvert.SerializeObject(response, jsonSerializerSettings);
Console.WriteLine(serializedResponse);
var roundTrippedResponse = JsonConvert.DeserializeObject<Response>(serializedResponse, jsonSerializerSettings);
Assert.That(roundTrippedResponse.data.children.First().GetType(), Is.EqualTo(typeof(Type1)));
Assert.That(roundTrippedResponse.data.children.Last().GetType(), Is.EqualTo(typeof(Type3)));
}
The output written to the console is:
{
"$type": "Test.Response, Test",
"schema": "Listing",
"data": {
"$type": "Test.ListingData, Test",
"key": "28ba648c-de24-45d4-a7d9-70f810cf5438",
"children": [
{
"$type": "Test.Type1, Test",
"body": "Four score and seven years ago...",
"parent_id": "2qh3l",
"report_count": 0,
"name": "c4j6yeh"
},
{
"$type": "Test.Type3, Test",
"domain": "abc.def.com",
"flagged": true,
"category": "news",
"saved": false,
"id": "t3dz0",
"created": 1335998011.0
}
]
}
}
So if you can transform your received response to match that of Json.NET's expected format, this will work.
To piece all of this together, you would need to write a custom MediaTypeFormatter and pass it to the ReadAsAsync<>() call.