I need to create objects in c# that get converted to the following JSON:
{
"Order": {
"CustomerCode": "9999999",
"Note": "New Order for Test -- UNIT TEST",
"Stops": {
"Stop": {
"Sequence": "1",
"StopType": "P",
"Name": "CVS"
},
"Stop": {
"OrderStopID": "5",
"Sequence": "2",
"StopType": "D",
}
},
"Jobs": {
"Job": {
"Sequence": "1",
"Drivers": {
"Driver": {
"Sequence": "1",
"DriverCode": "09"
},
"Driver": {
"Sequence": "2"
}
}
}
}
}
}
Here are the objects I created to represt this:
public class RootObject
{
public Order Order { get; set; }
}
public class Order
{
public string CustomerCode { get; set; }
public List<Stop> Stops { get; set; }
public List<Job> Jobs { get; set; }
}
When I use JSON.NET to serialize the root object I get the following:
Notice taht hte Stops and Jobs are generated as an array, how do I make it so it gets genrated as the JSON is showed at the beginning?
{
"Order": {
"CustomerCode": "9999999",
"Stops": [
{
"Sequence": "1",
"StopType": "P",
"Name": "CVS"
},
{
"OrderStopID": "5",
"Sequence": "2",
"StopType": "D",
}
],
"Jobs": [
{
"Sequence": "1",
"Drivers": [
{
"Sequence": "1",
"DriverCode": "09"
},
{
"Sequence": "2"
}
]
}
]
}
}
You can use a JsonConverter for this:
public class ArrayConverter : JsonConverter
{
private readonly string _propertyName;
public ArrayConverter(string propertyName)
{
this._propertyName = propertyName;
}
public override bool CanConvert(Type objectType)
{
return objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(List<>);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
IList items = (IList)Activator.CreateInstance(objectType);
var modelType = objectType.IsArray ? objectType.GetElementType() : objectType.GetGenericArguments().Single();
if (reader.TokenType != JsonToken.StartObject)
{
throw new ArgumentOutOfRangeException(nameof(reader), "Expected object.");
}
while (reader.Read() && reader.TokenType != JsonToken.EndObject)
{
reader.Read();
items.Add(serializer.Deserialize(reader, modelType));
}
return items;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is IEnumerable enumerableValue)
{
JObject obj = new JObject();
writer.WriteStartObject();
foreach (var val in enumerableValue)
{
writer.WritePropertyName(_propertyName);
serializer.Serialize(writer, val);
}
writer.WriteEndObject();
}
else
{
throw new ArgumentOutOfRangeException(nameof(value), "Value does not implement IEnumerable.");
}
}
}
Try it online
Related
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 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.
I'm trying to deserialize this json:
{
"teaser": [{
"id": "...",
"type": "category",
"url": "https:...",
},{
"id": "...",
"type": "brand",
"url": "https:...",
"videoCount": 1,
},{
"id": "...",
"type": "video",
"url": "https:...",
"headline": "...",
}]
}
It has a list of teasers whereby each teaser is different depending on its type.
These would be my objects:
public class StartPage
{
public IList<Teaser> Teaser { get; set; }
}
public abstract class Teaser
{
public string Id { get; set; }
public string Url { get; set; }
}
public class Video : Teaser
{
public string Headline { get; set; }
}
public class Brand : Teaser
{
public int VideoCount { get; set; }
}
I am new to Json.NET and Xamarin and couldn't find a solution for this case yet. Before, when I was using Android Studio and Gson, I could register sybtypes the following way:
RuntimeTypeAdapterFactory<Teaser> teaserRuntimeTypeAdapterFactory = RuntimeTypeAdapterFactory.of(
Teaser.class, "type")
.registerSubtype(Video.class, Teaser.TYPE_VIDEO)
.registerSubtype(Brand.class, Teaser.TYPE_BRAND)
.registerSubtype(Category.class, Teaser.TYPE_CATEGORY);
return new GsonBuilder()
.registerTypeAdapterFactory(teaserRuntimeTypeAdapterFactory);
Is there a similar way to achieve what I want with Json.NET I have overlooked yet?
What you can do is create custom JsonConverter please below snippet and find this dot net fiddle
string json ="{ 'Teaser': [{ 'id': '...', 'type': 'category', 'url': 'https:...', },{ 'id': '...', 'type': 'brand', 'url': 'https:...', 'videoCount': 1, },{ 'id': '...', 'type': 'video', 'url': 'https:...', 'headline': '...', }]}";
var list = JsonConvert.DeserializeObject<StartPage>(json);
public class BaseSpecifiedConcreteClassConverter : DefaultContractResolver
{
protected override JsonConverter ResolveContractConverter(Type objectType)
{
if (typeof(Teaser).IsAssignableFrom(objectType) && !objectType.IsAbstract)
return null; // pretend TableSortRuleConvert is not specified (thus avoiding a stack overflow)
return base.ResolveContractConverter(objectType);
}
}
public class BaseConverter : JsonConverter
{
static JsonSerializerSettings SpecifiedSubclassConversion = new JsonSerializerSettings() { ContractResolver = new BaseSpecifiedConcreteClassConverter() };
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Teaser));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
switch (jo["type"].Value<string>())
{
case "video":
return JsonConvert.DeserializeObject<Video>(jo.ToString(), SpecifiedSubclassConversion);
case "brand":
return JsonConvert.DeserializeObject<Brand>(jo.ToString(), SpecifiedSubclassConversion);
default:
throw new Exception();
}
throw new NotImplementedException();
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException(); // won't be called because CanWrite returns false
}
}
Feel free to create custom logic in switch case hard code class name might not good idea instead create enum or something like but this is how you can achieve this type of scenario
I am having issues figuring out how to parse a JSON response that I am getting back from a 3rd party. I need to convert from the following JSON (a simplified example of what I am getting back) to a C# class. The main issue is that the columns may be in a different order, have extra fields, or missing ones (Which should just end up as null values).
{
"columns": [
{ "id": { "type": "Numeric", "nullable": false } },
{ "name": { "type": "Text", "nullable": false } },
{ "description": { "type": "Text", "nullable": true } },
{ "last_updated": { "type": "DateTime", "nullable": false } }
],
"rows": [
[1, "foo", "Lorem ipsum", "2016-10-26T00:09:14Z"],
[4, "bar", null, "2013-07-01T13:04:24Z"]
]
}
The C# class for this example would be
public class Record
{
public int id { get; set; }
public string name { get; set; }
public string description { get; set; }
public DateTime? last_updated { get; set; }
}
I have tried using a custom json converter, but did not have much luck getting it to work with the separated metadata and values. Does anyone have any ideas on how to go about parsing this kind of data? Eventually there will be multiple "Record" types, which is why the response from the server can be dynamic.
Your question is similar to this recent one and can be solved using a similar converter:
public class RowColumnListConverter<T> : JsonConverter
{
const string columnsKey = "columns";
const string rowsKey = "rows";
public override bool CanConvert(Type objectType)
{
if (!typeof(ICollection<T>).IsAssignableFrom(objectType))
return false;
// This converter is only implemented for read/write collections. So no arrays.
if (objectType.IsArray)
return false;
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var list = existingValue as ICollection<T> ?? (ICollection<T>)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
var root = JObject.Load(reader);
var rows = root.GetValue(rowsKey, StringComparison.OrdinalIgnoreCase).NullCast<JArray>();
if (rows == null)
return list;
var columns = root.GetValue(columnsKey, StringComparison.OrdinalIgnoreCase).NullCast<JArray>();
if (columns == null)
throw new JsonSerializationException(columnsKey + " not found.");
var columnNames = columns.Cast<JObject>().SelectMany(o => o.Properties()).Select(p => p.Name).ToArray();
foreach (var row in rows)
{
if (row == null || row.Type == JTokenType.Null)
list.Add(default(T));
else if (row.Type == JTokenType.Array)
{
var o = new JObject(columnNames.Zip(row, (c, r) => new JProperty(c, r)));
list.Add(o.ToObject<T>(serializer));
}
else
throw new JsonSerializationException(string.Format("Row was not an array: {0}", row));
}
return list;
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public static class JsonExtensions
{
public static TJTOken NullCast<TJTOken>(this JToken token) where TJTOken : JToken
{
if (token == null || token.Type == JTokenType.Null)
return null;
var typedToken = token as TJTOken;
if (typedToken == null)
throw new JsonSerializationException(string.Format("Token {0} was not of type {1}", token.ToString(Formatting.None), typeof(TJTOken)));
return typedToken;
}
}
Then use it like:
var list = JsonConvert.DeserializeObject<List<Record>>(json, new RowColumnListConverter<Record>());
Or
var list = JsonConvert.DeserializeObject<List<Record>>(json, new JsonSerializerSettings
{
Converters = { new RowColumnListConverter<Record>() },
});
The converter works by loading the outer object into a temporary JObject, then reformatting the "columns" and "rows" arrays into a more conventional list of objects for deserialization. Note that no attempt is made to use the type information in the "columns" list, it is simply assumed that the POCO members have the correct type. Also, WriteJson() is not implemented since there isn't enough information in the question to determine how to emit the type information for any possible column type; a full specification would be required.
Sample fiddle.
i think this approach is tricky somehow, but if you have to follow this, you can put order property for them to solve your order problem:
{
"columns": [
{ "id": { "type": "Numeric", "nullable": false, "order":1 } },
{ "name": { "type": "Text", "nullable": false, "order":2 } },
{ "description": { "type": "Text", "nullable": true, "order":3 } },
{ "last_updated": { "type": "DateTime", "nullable": false, "order":4 } }
],
"rows": [
[1, "foo", "Lorem ipsum", "2016-10-26T00:09:14Z"],
[4, "bar", null, "2013-07-01T13:04:24Z"]
]
}
also for null value you can get a default value to recognize them and replace with null in your custom json converter.
the best structure for your data can be:
public class Column
{
public string type { get; set; }
public bool nullable { get; set; }
public int order { get; set; }
}
public class Model
{
public List<Dictionary<string, Column>> columns { get; set; }
public List<List<string>> rows { get; set; }
}
you can convert your json to the a Model class directly.
How can I check if a JSON output returned just 1 row of an element or multiple rows.
For example, I have the below JSON output.
{
"orders": {
"order": [
{
"id": 100,
"type": "market",
"symbol": "AAPL",
"side": "buy"
}]
}
}
I have the below code for deserializing the JSON output:
dynamic dynObj = JsonConvert.DeserializeObject(response);
Console.WriteLine("ID: {0}\n Type: {1}\n Symbol: {2}\n Side: {3}\n", dynObj.orders.order.id, dynObj.orders.order.type,dynObj.orders.order.symbol,dynObj.orders.order.side;
But, most of the times I get the output as an array of order object as below
{
"orders": {
"order": [
{
"id": 101,
"type": "market",
"symbol": "AAPL",
"side": "buy"
},
{
"id": 102,
"type": "market",
"symbol": "MSFT",
"side": "buy"
},
{
"id": 103,
"type": "limit",
"symbol": "AMZN",
"side": "buy"
}
]
}
}
So I deserialize the above Json output as shown:
dynamic dynObj = JsonConvert.DeserializeObject(response);
foreach (var c in dynObj.orders.order)
{
Console.WriteLine("Id : {0}\n", c.id);
Console.WriteLine("Type : {0}\n", c.type);
Console.WriteLine("Symbol : {0}\n", c.symbol);
Console.WriteLine("Side : {0}\n", c.side);
}
I have two important questions:
How can I know whether the JSON returned is only 1 row of Order
object or a collection so that I can use the relevant way to loop.
If it is an array, do I need to deserialize as below and if so, how
do I add them to Order Class/Object?
RootObject ord = JsonConvert.DeserializeObject(response);
Any help is greatly appreciated.
I would highly recommend you stop using dynamic because I think this is an abuse of the type. Instead I would define types to deserialize into. By doing so you give yourself type safety and working with the data becomes trivial. Below are class definitions an example using the generic deserialize method (Deserialize<T>(string input)).
public class order
{
public int id { get; set; }
public string type { get; set; }
public string symbol { get; set; }
public string side { get; set; }
}
public class orders
{
List<order> order { get; set; }
}
JsonSerializer serializer = new JsonSerializer();
orders myOrders = serializer.Deserialize<orders>(response);
if (myOrders.order.Count() == 1)
// we have 1 order
Able to fix the issue pertaining to check if the JSON result returned is an object or an array.
Modified my RootObject which has the Array/Object as follows:
[JsonConverter(typeof(GenericListCreationJsonConverter<T>))]
public List<T> order { get; set; }
Added the below class which checks for JSON
internal class GenericListCreationJsonConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override bool CanRead
{
get { return true; }
}
public override bool CanWrite
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
{
return serializer.Deserialize<List<T>>(reader);
}
else
{
T t = serializer.Deserialize<T>(reader);
return new List<T>(new[] { t });
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}