JSON deserialization for polymorphic results - c#

I have the following problem which is driving me crazy to find a proper solution.
I have to consume two RESTful APIs which return the same structure except for the items structure.
Let me give you two examples:
{
"hasmoredata":true,
"current_page": 1,
"page_size": 20,
"total_pages": 5,
"items": [
{
"user_id": "1",
"username": "carl",
"first_name": "carl",
}
]
}
{
"hasmoredata":true,
"current_page": 1,
"page_size": 10,
"total_pages": 2,
"items": [
{
"course_id": "10",
"course_name": "Math",
"duration": "3h",
}
]
}
I'd like to have two classes which extend an abstract one that collect the common properties. Something like this (in C#):
public abstract class CursorResult
{
[JsonProperty("current_page")]
public int CurrentPage { get; set; }
[JsonProperty("page_size")]
public int PageSize { get; set; }
[JsonProperty("total_pages")]
public int TotalPages { get; set; }
[JsonProperty("hasmoredata")]
public bool HasMoreData{ get; set; }
}
public class UsersList : CursorResult
{
[JsonProperty("items")]
List<User> Users { get; set; }
}
public class CoursesList : CursorResult
{
[JsonProperty("items")]
List<Courses> Courses { get; set; }
}
Now the problem is the fact that i have to write a function that collect the entire result (all the pages of items) and merge those results in one:
private CursorResult GetEntireResult(string apiURL)
{
Cursor c = new Cursor(1, pageSize);
CursorResult result = TryDeserializeCursorResult(CallRestFulAPI(apiURL + c.GetCursorParametersString, Method.GET));
c.Hashcode = result.CursorHashCode;
while (result.HasMoreData)
{
c.CurrentPage += 1;
result.AddItems(TryDeserializeCursorResult(CallRestFulAPI(apiURL + c.ParametersString, Method.GET)));
}
return result;
}
but i don't have any idea on how write the AddItem function in order to add Users or Courses depending on the API result.
Thanks in advance for any help!
Lapo

A couple of things:
With your current code, assuming you're using a default serializer, when you deserialize you're not going to capture 'items' because you're deserializing to CursorResult which doesn't have an 'items' property defined. The serializer doesn't automatically know about derived types. Also I would recommend renaming the UserList and CoursesList classes to UserCursorResult/CourseCursorResult. The classes aren't lists, they contain lists.
Here is code that will discriminate between the json serialized (sub)types :
string yourJsonString = #"{ 'hasmoredata':true,'current_page':
1,'page_size': 20,'total_pages': 5,'items': [{'user_id':
'1','username': 'carl','first_name': 'carl'}]}";
JSchemaGenerator generator = new JSchemaGenerator();
JSchema userSchema = generator.Generate(typeof(UsersList));
JSchema courseSchema = generator.Generate(typeof(CoursesList));
JToken jObject = JObject.Parse(yourJsonString);
if (jObject.IsValid(courseSchema))
{
CoursesList courseList = JsonConvert.DeserializeObject<CoursesList>(yourJsonString);
//do stuff with CourseList
}
else if (jObject.IsValid(userSchema))
{
UsersList userList = JsonConvert.DeserializeObject<UsersList>(yourJsonString);
//do stuff with UsersList
}

Related

Getting data in Json.Net c#

I want to get data from json file correctly. The json data file I modeled for this is as follows:
{
"Free title 1":[
{
"Subject": "a1",
"Relation": "a2"
},
{
"Subject": "b1",
"Relation": "b2"
}
],
"Another free title":[
{
"Subject": "z1",
"Relation": "z2"
},
{
"Subject": "y1",
"Relation": "y2"
}
],
"Unordered title":[
{
"Subject": "x1",
"Relation": "x2"
},
{
"Subject": "w1",
"Relation": "w2"
}
]
}
This is how I create an object class:
public class _Infos_
{
public List<_Info_> Infos { get; set; }
}
public class _Info_
{
public string Subject { get; set; }
public string Relation { get; set; }
}
And finally I'm trying to get the data in a method like this:
var js = JsonConvert.DeserializeObject<_Infos_>(File.ReadAllText("__FILE_PATH__"));
foreach (var j in js.Infos)
{
MessageBox.Show(j.Subject);
}
I get the error that js is empty. Here I want to get Free title 1, Another free title and Unordered title in a list. Of course, these titles will be constantly changing. Afterwards, I want to get the Subject and Relation data under these titles. But I have no idea how to get it.
This data structure is a dictionary of collections of _Info_s. You need to deserialize it to Dictionary<string, List<_Info_>>.
Here are System.Text.Json and Json.net examples:
var d = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, List<_Info_>>>(json);
var d2 = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, List<_Info_>>>(json);
Your class definition is a little wrong.
You can use online tools "json to c#" to generate the correct classes.
like this one: https://json2csharp.com
Your "root" of your json for example does not contain an array in your json. The property "Free title 1":[..] is an array, so your root needs a property with the name FreeTitle1 and it has to be an array/list.
public class Root
{
[JsonProperty("Free title 1")]
public List<TitleInfo> FreeTitle1 { get; set; }
[JsonProperty("Another free title")]
public List<TitleInfo> AnotherFreeTitle { get; set; }
[JsonProperty("Unordered title")]
public List<TitleInfo> UnorderedTitle { get; set; }
}
public class TitleInfo
{
public string Subject { get; set; }
public string Relation { get; set; }
}
If your object members have dynamic names, you can also manually deserialize the object, e.g. using the general type JObject. E.g.
JObject obj = JObject.Parse(File.ReadAllText("__FILE_PATH__"));
JObject implements IEnumerable<KeyValuePair<string, JToken>> over which you can iterate.
Each member will then have JToken Value, which is a JArray in this case, which you can cast to a List of your type.
foreach (var groups in obj)
{
var infos = groups.Value.ToObject<List<_Info_>>();
// .. loop over infos
}

How to deserialise or format WebAPI result into specific json structure

I was working with a .net core 3.1 Web API. Which is getting data from an external API. Following is my code Controller part
[HttpGet("transinfo/{id}")]
public Object GettransactionData(int id)
{
var result=_transaction.GettransactionDetails(id).Result;
List<PipeLineResponse> P = JsonConvert.DeserializeObject<List<PipeLineResponse>>(result.ToString());
PipeLineResponseObject P1 = new PipeLineResponseObject();
P1.data = P;
return P1;
}
And my service code as follows
public async Task<Object> GettransactionDetails(int id)
{
string request=//fetched from db
var stringContent = new StringContent(request);
Client = utilities.GetHttpClient();
string apiEndpoint=//External API URL
HttpResponseMessage httpResponseMessage = await Client.PostAsync(apiEndpoint, stringContent);
if (httpResponseMessage.IsSuccessStatusCode)
{
return await httpResponseMessage.Content.ReadAsAsync<Object>();
}
}
But i am getting the result in following format (response from postman)
{
"data": [
{
"Tranid": "34540d40-7db8-44c1-9a2a-5072c2d01756",
"fields": {
"Fields.10": "1001",
"Fields.11": "Test1",
"Fields.12": "Fixed1"
}
},
{
"Tranid": "145800f9-c4a5-4625-84d7-29af5e674a14",
"fields": {
"Fields.10": "1002",
"Fields.11": "Test2",
"Fields.12": "Fixed2"
}
}
]
}
But i need the data in following format
{
"data": [
{
"TransactionID": "34540d40-7db8-44c1-9a2a-5072c2d01756",
"fieldsList": [
{
"fieldId": "10",
"fieldValue": "1001"
},
{
"fieldId": "11",
"fieldValue": "Test1"
},
{
"fieldId": "12",
"fieldValue": "Fixed1"
}
]
},
{
"TransactionID": "145800f9-c4a5-4625-84d7-29af5e674a14",
"fieldsList": [
{
"fieldId": "10",
"fieldValue": "1002"
},
{
"fieldId": "11",
"fieldValue": "Test2"
},
{
"fieldId": "12",
"fieldValue": "Fixed2"
}
]
}
]
}
How can i achieve this ? is possible to deserialise using JObject or JArray? Please help.
i have tried to create following model class and tried to deserialise but not getting result as expected.
public class PipeLineResponse
{
public string TransactionID { get; set; }
public List<Dictionary<string, string>> fields { get; set; }
}
public class PipeLineResponseObject
{
public List<PipeLineResponse> data { get; set; }
}
How to create that json in that format any DTO or Automapper will work ? Please help me with samples.
The solution that I am laying down here takes the DTO approach. The response from the service is being deserialized to the DTO, which further is being manually mapped to the final ViewModel that we are sending to the client. By no means, this implementation is production-ready and there is scope for improvement, for which I am adding in comments. But this gives a detailed understanding of how we can handle these kind of scenarios. We are making use of Newtonsoft.Json, which can be pulled into your project via the NuGet package manager.
Structure of the DTO
// RootDTO.cs
// This structure is directly based on the response obtained from remote service.
public class Fields
{
[JsonProperty(PropertyName ="Fields.10")]
public string Fields10 { get; set; }
[JsonProperty(PropertyName = "Fields.11")]
public string Fields11 { get; set; }
[JsonProperty(PropertyName = "Fields.12")]
public string Fields12 { get; set; }
}
public class Datum
{
public string Tranid { get; set; }
public Fields fields { get; set; }
}
public class RootDTO
{
[JsonProperty(PropertyName ="data")]
public List<Datum> data { get; set; }
}
Structure of ViewModel
// PipelineResponse.cs
public class FieldsList
{
public string fieldId { get; set; }
public string fieldValue { get; set; }
}
public class ResponseDatum
{
[JsonProperty(PropertyName = "TransactionID")]
public string TransactionID { get; set; }
public List<FieldsList> fieldsList { get; set; }
}
public class PipelineResponse
{
public List<ResponseDatum> data { get; set; }
}
Deserializing the response to the DTO
// ...other code
var responseString = await httpResponseMessage.Content.ReadAsAsync<Object>();
// This is where the DTO object is created. This should be mapped to view model type.
var responseDTO = JsonConvert.DeserializeObject<RootDTO>(responseString);
Mapping the DTO to ViewModel
The mapping from DTO type to ViewModel type needs to be done before sending the response to the client. It is the view model type that is sent to the client. This logic can be placed within a separate helper (ideally, to separate concerns) or any other location as per the practices you are following.
public PipelineResponse ConvertResponseDTOToResponse(RootDTO responseDTO)
{
// FieldId is being hardcoded here. Instead, you can use Reflection to
// fetch the property name, split on '.' and take the item at index 1.
// Notice that DTO properties have "JsonProperty" attributes for this.
try
{
List<ResponseDatum> responseList = new List<ResponseDatum>();
if (responseDTO != null)
{
// Reflection can be used to avoid hardcoding on 'fieldId'
foreach (var item in responseDTO.data)
{
var responseDataObj = new ResponseDatum
{
TransactionID = item.Tranid,
fieldsList = new List<FieldsList>
{
new FieldsList
{
fieldValue = item.fields.Fields10,
fieldId = "10"
},
new FieldsList
{
fieldValue = item.fields.Fields11,
fieldId = "11"
},
new FieldsList
{
fieldValue = item.fields.Fields12,
fieldId = "12"
}
}
};
responseList.Add(responseDataObj);
}
}
// This object is what you return from your controller endpoint finally.
// The serialized response of this object is of the json structure you need
return new PipelineResponse { data = responseList };
}
catch (Exception ex)
{
throw ex;
}
}

How can I deserialize a JSON string which has objects from different classes?

I would like to deserialize a JSON string which contains multiple object types. So for instance here are my two POCO classes:
public class Car
{
[Key]
public int Id { get; set; }
public string Color { get; set; }
}
public class Driver
{
[Key]
public int Id { get; set; }
public string Firstname { get; set; }
}
Now I would like to have a JSON string which would have an array of objects from both classes (which are unrelated) and that would get deserialized into their respective POCO classes using the Newtonsoft json package in Visual Studio.
Is this possible and if so, what does the JSON format look like? An example would be appreciated with just 2 objects per class to show the array of objects and the two class types coexisting within a single JSON string which is passed to something like JsonConvert.DeserializeObject.
EDIT: the JSON for these classes and an object within would individually look something like:
for Car...
[
{
"color": "red"
},
{
"color": "blue"
}
]
and for Driver...
[
{
"Firstname": "Fred"
},
{
"Firstname": "Sally"
}
]
But now could those be combined into one string, but with some sort of extra parm to define to which CLASS each set belongs such as:
"Car"
[
{
"color": "red"
},
{
"color": "blue"
}
],
"Driver"
[
{
"Firstname": "Fred"
},
{
"Firstname": "Sally"
}
]
See how this has 2 unrelated classes in a single JSON string? Now I'd like to do a single (if possible) Jsonconvert into the respective classes so that "Car" winds up with 2 objects and "Driver" winds up with 2 (for this example).
Could be done like this:
using System.Collections;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
var objArray = new ArrayList
{
new Car {Id = 1, Color = "blue"},
new Driver {Id = 1, Firstname = "John"},
new Car {Id = 2, Color = "blue"},
new Driver {Id = 2, Firstname = "Jane"}
};
var json = JsonConvert.SerializeObject(objArray);
var jArray = JArray.Parse(json);
foreach (var child in jArray.Children())
{
if (child.Children().Any(token => token.Path.Contains("Color")))
{
// we got a Car
var car = ((JObject) child).ToObject<Car>();
}
else if (child.Children().Any(token => token.Path.Contains("Firstname")))
{
// we got a Driver
var driver = ((JObject) child).ToObject<Driver>();
}
}
}
}
public class Car
{
public int Id { get; set; }
public string Color { get; set; }
}
public class Driver
{
public int Id { get; set; }
public string Firstname { get; set; }
}
}

Deserializing this object in JSON.NET

I have the following object:
{
"pickups": {
"7": [
5,
8
],
"10": [
6,
7,
9
],
"15": [
1
],
"20": [
0,
2
],
"25": [
3,
4
]
}
}
I'd like to de-serialize each pickups element into the following object:
public class Pickups {
public Pickup[] pickups;
}
public class Pickup {
public int Group; // This could be the 7, 10, 15, 20, 25, etc.
public int[] Values; // If this was the "7" grouping, it would contain 5, 8.
}
As you can see from the data its a bit tricky to do this. I've been trying to use a JsonConverter to convert the object with a bit of custom code but its been a nightmare and I haven't been able to get it right. I am wondering if anyone would know the best way to convert this type of object into the correct format I need?
While a converter would be a good choice you can still deserialize the Json and construct the desired object graph
var root = JsonConvert.DeserializeObject<RootObject>(json);
var pickups = new Pickups {
pickups = root.pickups.Select(kvp =>
new Pickup {
Group = int.Parse(kvp.Key),
Values = kvp.Value
}
).ToArray()
};
Where
public class RootObject {
public IDictionary<string, int[]> pickups { get; set; }
}
This is what son2csharp.com says, its gets error because you can not define names with starting number.
public class Pickups
{
public List<int> __invalid_name__7 { get; set; }
public List<int> __invalid_name__10 { get; set; }
public List<int> __invalid_name__15 { get; set; }
public List<int> __invalid_name__20 { get; set; }
public List<int> __invalid_name__25 { get; set; }
}
public class RootObject
{
public Pickups pickups { get; set; }
}
But I think
[DataMember(Name = "Name")]
should work cause its not an error in JSON format side.
If it is a viable option for you to use JObject.Parse(...) instead, you could use the following code (and write it more cleanly, with exception handling and safe casts and so on):
var jsonPickups = JObject.Parse(json);
var myPickups = new Pickups
{
pickups = jsonPickups.First.First.Select(x =>
{
JProperty xProp = x as JProperty;
return new Pickup
{
Group = int.Parse(xProp.Name),
Values = (xProp.Value as JArray).Select(y => int.Parse(y.ToString())).ToArray()
};
}).ToArray()
};

Deserialize multiple JSON arrays into separate C# datasets/datatables using JSON.net (newtonsoft)

Im trying for hours now to get the following two JSON arrays (namely clients and dossiers) into two separate datasets/datatables using Newtonsoft JSON.net in C#:
{
"status": "OK",
"clients": [
{
"ClientID": "123456",
"Fullname": "John Doe",
"Inactive": false
},
{
"ClientID": "234567",
"Fullname": "James Smith",
"Inactive": false
}
],
"dossiers": [
{
"CreateDate": "03.06.2013",
"DossierName": "JD20130603"
},
{
"CreateDate": "04.06.2013",
"DossierName": "JS20130604"
}
]
}
Can someone please help? Thanks in advance...
EDIT: I would like to avoid the whole Class thing if possible.
EDIT 2: So far Ive tried the following approaches
var _clientlist = JObject.Parse(_jsonresp)["clients"].Children();
Which works, but I cannot get the values into a dataset/datable
_clientlist = (DataTable)JsonConvert.DeserializeObject(_jsonresp, (typeof(DataTable)));
Fails :(
DataSet _dataset = JsonConvert.DeserializeObject<DataSet>(_jsonresp);
DataTable _clientlist = _dataset.Tables["clients"];
Similar process to above but same result
dynamic _d = JValue.Parse(_response);
JArray _jsonval = JArray.Parse(_d.clients) as JArray;
Fails :(
At which point I gave up.
This doesn't answer the question exactly because I personally don't see why you would want to deserialize into datasets when the json.NET model is more oriented around deserializing into objects. Here it is;
public class TheJson
{
public string status { get; set; }
public client[] clients { get; set; }
public dossier[] dossiers { get; set; }
}
public class client
{
public string ClientID { get; set; }
public string Fullname { get; set; }
public bool Inactive { get; set; }
}
public class dossier
{
public string CreateDate { get; set; }
public string DossierName { get; set; }
}
With those definitions it's as simple as;
TheJson clientsAndDossiers = JsonConvert.DeserializeObject<TheJson>(_jsonresp);
Now with regard to your last comment, to apply search filters I would just use LINQ. Like for example if I want to get only active clients I could do;
List<client> activeClients = clientsAndDossiers.clients.Where(x => x.Inactive == false).ToList();
With regard your comment on this post, here is the LINQ implementation;
string inputString = MenuSearchBox.Text.ToString();
List<client> filtered = clientsAndDossiers.clients.Where(x => x.Fullname.Contains(inputString)).ToList();

Categories