I have the following JSON which is meant to define a form, but each "Section" (e.g. Training, Education), is not in an array.
What I need is for each of the top sections (e.g. Training, etc.) to be converted to a C# Section class, and the fields under that a List<Field>, etc. so that it can then be rendered.
Here is the JSON I am working with. I have tried various ways of deserialising but none work.
{
"readonly": "True",
"Training": {
"entity": "PNLCAND",
"type": "standard",
"mode": "update",
"fields": {
"HEADER_3": {
"caption": "Please enter all relevant work related courses",
"type": "header",
"update": "",
"mandatory": false,
"tooltip": ""
},
"HEADER_5": {
"caption": "",
"type": "header",
"update": "",
"mandatory": false,
"tooltip": ""
},
"HEADER_6": {
"caption": "",
"type": "header",
"update": "",
"mandatory": false,
"tooltip": ""
}
}
},
"Education": {
"entity": "PNLCANQUA",
"type": "repeating",
"mode": "update",
"fields": {
"Q_SUBJECT": {
"caption": "Qualifications gained",
"type": "string",
"length": 40,
"update": "Y",
"mandatory": false,
"tooltip": ""
},
"TO_DATE": {
"caption": "Date Awarded/Expected",
"type": "date",
"range": {
"start": -60,
"end": 0
},
"update": "Y",
"mandatory": false,
"tooltip": ""
},
"Q_GRADE": {
"caption": "Grade/Level",
"type": "string",
"length": 40,
"update": "Y",
"mandatory": false,
"tooltip": ""
},
"Q_LOCATION": {
"caption": "School / Colleges, Universities or Institutes of Further Education",
"type": "string",
"length": 40,
"update": "Y",
"mandatory": false,
"tooltip": ""
}
},
"values": [
{
"TO_DATE": "2019-08-01",
"Q_SUBJECT": "Qual 1",
"Q_LOCATION": "School",
"Q_GRADE": "a",
"UDF_6_EDU001": "C"
},
{
"TO_DATE": "2019-08-01",
"Q_SUBJECT": "Qual 2",
"Q_LOCATION": "School",
"Q_GRADE": "a",
"UDF_6_EDU001": "C"
},
{
"TO_DATE": "2019-08-31",
"Q_SUBJECT": "Qual 3",
"Q_LOCATION": "Uni",
"UDF_6_EDU001": "U"
}
]
},
}
You will need to use a custom JsonConverter if you want to deserialize the top-level sections and the variably-named fields within them into lists. It is possible to write the converter in a generic way which will handle both the sections and the fields.
First we need to define a model to capture the data:
public class Section
{
public string Name { get; set; }
public string Entity { get; set; }
public string Type { get; set; }
public string Mode { get; set; }
public List<Field> Fields { get; set; }
public List<Dictionary<string, string>> Values { get; set; }
}
public class Field
{
public string Name { get; set; }
public string Caption { get; set; }
public string Type { get; set; }
public Range Range { get; set; }
public int Length { get; set; }
public string Update { get; set; }
public bool Mandatory { get; set; }
}
public class Range
{
public int Start { get; set; }
public int End { get; set; }
}
Here is a generic JsonConverter which will handle creating the lists. The T generic parameter on the class specifies the item type to use for the list (e.g. Section or Field). The keyPropertyName constructor parameter tells the converter which property on the model should be used to store the item's key from the JSON. I added Name properties to both Section and Field for this purpose, so "Name" should be passed in both cases.
The converter works by loading the JSON into a JObject and iterating over its properties, excluding any whose values are not objects (so this will skip over the "readonly" property at the top level, which is obviously not a section). The remaining property values are converted to model objects using the ToObject<T> method (after first setting the name on it) and added to the return list.
public class ObjectToListConverter<T> : JsonConverter
{
public string KeyPropertyName { get; set; }
public ObjectToListConverter(string keyPropertyName)
{
KeyPropertyName = keyPropertyName;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(List<T>);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject obj = JObject.Load(reader);
List<T> list = new List<T>();
foreach (JProperty prop in obj.Properties().Where(p => p.Value.Type == JTokenType.Object))
{
JToken item = prop.Value;
item[KeyPropertyName] = prop.Name;
list.Add(item.ToObject<T>(serializer));
}
return list;
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use this converter, pass an instance for both Section and Field to JsonConvert.DeserializeObject like this:
List<Section> sections = JsonConvert.DeserializeObject<List<Section>>(json,
new ObjectToListConverter<Section>("Name"), new ObjectToListConverter<Field>("Name"));
Here is a working demo: https://dotnetfiddle.net/A4uAGk
You have to create a Training class it looks exactly how your json structure. Then use the dapper/automapper to bind the section model object.
Related
I would like my output JSON to contain a simple array shown below
{
"attributes":[
{
"trait_type": "Background",
"value": "Green"
},
{
"trait_type": "Body",
"value": "Body_1"
},
{
"trait_type": "Outfit",
"value": "Beach_Singlet"
},
{
"display_type":"date",
"trait_type":"birthday",
"value":869270400
}
]
}
Notice how the last item is different from the previous items in the array. The variable named "value" is also an integer as compared to the previous entries as strings.
How do I go about in order to be able to output my JSON as shown above? I have tried creating a class that can store all the information, but I cannot reuse the name "value" for both an int and string declaration, and also do not wish to show the variables if their value is null
(Example shown below)
{
"attributes": [
{
"display_type": "",
"trait_type": "Background",
"value": "Green"
},
{
"display_type": "",
"trait_type": "Body",
"value": "Body_1"
},
{
"display_type": "",
"trait_type": "Outfit",
"value": "Beach_Singlet"
},
{
"display_type": "date",
"trait_type": "birthday",
"value": 869270400
}
]
}
You can use object type.
using Newtonsoft.Json;
var list = new AttributeList
{
attributes = new []{
new Attribute
{
trait_type = "Background",
value = "green"
},
new Attribute
{
display_type = "date",
trait_type = "birthday",
value = 869270400
}
}
};
var json = JsonConvert.SerializeObject(list, Formatting.Indented);
Console.WriteLine(json);
public class Attribute
{
public object value { get; set; }
public string trait_type { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string display_type { get; set; }
}
public class AttributeList
{
public Attribute[] attributes { get; set; }
}
Output:
{
"attributes": [
{
"value": "green",
"trait_type": "Background"
},
{
"value": 869270400,
"trait_type": "birthday",
"display_type": "date"
}
]
}
try this
var attributes=new List<Attribute>{
new AttributeString{
trait_type="Background",
value="green"
},
new AttributeInt{
display_type ="date",
trait_type="birthday",
value=869270400
}
};
var jsonSerializerSettings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Objects,
NullValueHandling=NullValueHandling.Ignore,
Formatting=Newtonsoft.Json.Formatting.Indented
};
var json = JsonConvert.SerializeObject(attributes,jsonSerializerSettings);
classes
public class Attribute
{
public string trait_type { get; set; }
public string display_type { get; set; }
}
public class AttributeString:Attribute
{
public string value { get; set; }
}
public class AttributeInt:Attribute
{
public int value { get; set; }
}
public class AttributeList
{
public List<Attribute> attributes { get; set; }
}
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 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
In my application I want to show a folder and its containing bookmarks. I try to achieve something like this:
folder Wikipedia
url a
url b
url ...
folder StackOverflow
url a
url b
Therefore I have to parse the following json string:
{
"checksum": "7d7205349eb64a4894aafc5ce074c0c0",
"roots": {
"bookmark_bar": {
"children": [ {
"children": [ {
"date_added": "13021579661026871",
"id": "28",
"name": "design patterns - Do you allow the Web Tier to access the DAL directly? - Stack Overflow",
"type": "url",
"url": "http://stackoverflow.com/questions/796656/do-you-allow-the-web-tier-to-access-the-dal-directly"
}, {
"date_added": "13021665700468056",
"id": "31",
"name": "VS 2010 Error when creating or opening projects - Stack Overflow",
"type": "url",
"url": "http://stackoverflow.com/questions/8403853/vs-2010-error-when-creating-or-opening-projects"
} ],
"date_added": "13021579680308871",
"date_modified": "13024947520078515",
"id": "29",
"name": "StackOverflow",
"type": "folder"
}, {
"children": [ {
"date_added": "13022096980978880",
"id": "45",
"name": "Dependency injection - Wikipedia, the free encyclopedia",
"type": "url",
"url": "http://en.wikipedia.org/wiki/Dependency_injection"
}, {
"date_added": "13024941326636844",
"id": "124",
"name": "Strategy pattern - Wikipedia, the free encyclopedia",
"type": "url",
"url": "http://en.wikipedia.org/wiki/Strategy_pattern"
} ],
"date_added": "13023315356559470",
"date_modified": "13024946156966435",
"id": "72",
"name": "Wiki",
"type": "folder"
}, {
"children": [ {
"date_added": "13023667785042757",
"id": "85",
"name": "Anemic Domain Model Illustrated | Frequent incoherent cogitation",
"type": "url",
"url": "http://vitamic.wordpress.com/2007/01/04/anemic-domain-model-illustrated/"
} ],
"date_added": "13023667668403520",
"date_modified": "13023668043391377",
"id": "82",
"name": "#Read",
"type": "folder"
}, {
"date_added": "13025102943539897",
"id": "130",
"name": "Modern UI for WPF - Home",
"type": "url",
"url": "http://mui.codeplex.com/wikipage?title=screenshots&referringTitle=Home"
} ],
"date_added": "13020681767991841",
"date_modified": "13025102947408897",
"id": "1",
"name": "Lesezeichenleiste",
"type": "folder"
}
},
"version": 1
}
I have tried the GroupBy Function, like this without success:
var items = jObject.Descendants()
.Where(x => x.Type == JTokenType.Object &&
x.Value<string>("type") != null)
.GroupBy(x => x.Value<string>("type"));
foreach (var item in items)
{
Console.WriteLine(item.Key.ToString());
foreach (var children in item)
{
Console.WriteLine(" " + children.Value<string>("name"));
}
}
I have also tried do apply the Join Function but I am missing a join property here. Can someone point me in the right direction please?
I would parse that json using concrete classes.
var root = JsonConvert.DeserializeObject<RootObj>(json);
Print(root.roots.bookmark_bar,"");
void Print(Node n,string padding)
{
Console.WriteLine(padding + "+" + n.name);
foreach(var url in n.children.Where(c => c.type == "url"))
{
Console.WriteLine(padding + "\t-" + url.name);
}
foreach (var folder in n.children.Where(c => c.type == "folder"))
{
Print(folder, padding + "\t");
}
}
public class Node
{
public string date_added { get; set; }
public string date_modified { get; set; }
public string id { get; set; }
public string name { get; set; }
public string type { get; set; }
public string url { get; set; }
public List<Node> children { get; set; }
}
public class Roots
{
public Node bookmark_bar { get; set; }
}
public class RootObj
{
public string checksum { get; set; }
public Roots roots { get; set; }
public int version { get; set; }
}
Above code is enough to parse your json, but if you want *date_modified* and *date_added* fields as DateTime, you can implement a JsonConverter class
var root = JsonConvert.DeserializeObject<RootObj>(json, new DateTimeConverter());
class DateTimeConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DateTime);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return new DateTime(1970,1,1).Add(TimeSpan.FromTicks(long.Parse((string)reader.Value)));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then your Node class will be
public class Node
{
public DateTime date_added { get; set; }
public DateTime date_modified { get; set; }
public string id { get; set; }
public string name { get; set; }
public string type { get; set; }
public string url { get; set; }
public List<Node> children { get; set; }
}
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.