I am writing some integration with a third-party eLearning platform that returns a variety of responses in different schemas depending on the function of my restful API call. Since these responses come back in several different schemas, I'm trying to create a series of response object classes that inherit a base Response object class that would contain the common JSON sections (aka "data" and "message") and allow each individual response object to override or have additional members/classes based on the response being returned.
Here are a couple examples of how the schemas may differ.
Class Creation Return:
{
"data": [
{
"row_index": 0,
"success": true,
"message": "string"
}
]
}
User Creation Return:
{
"data": [
{
"message": [
{
"id": "string",
"message": "string"
}
],
"success": true,
"user_id": 0
}
]
}
As you can see, the different responses have different schemas. The Class Create only returns a message member within the data object, and the User Create has a separate message object altogether.
Since I can't have a class called data within each object because of ambiguity, I'm thinking I need to create a Base Response Object that contains the common members and allows me to override or add on the fly as necessary.
I've tried to create a Base Class:
public class BaseResponse
{
public List<Data> data;
public class Data
{
public bool success { set; get; }
}
}
as well as an example derived class:
public class ClassroomResponse : BaseResponse
{
public class Data
{
public int row_index { get; set; }
public string message { get; set; }
}
}
I'm not sure if this is only possible with functions and not classes as I'm trying to do above? Is there a way to add additional members to the derived object's (row_index and message are not members of all responses, so I'd like to be able to grab those as needed)
You could either create individual, distinct classes for each type, which might be the right option here depending on the other variants you haven't shown. Or you can use generics. There's a few ways you can do this, but here is one way you might do it.
A base class for the overall response:
public abstract class Response<TData>
{
[JsonProperty("data")]
public TData Data { get; set; }
}
A base class for the Data objects:
public abstract class BaseData<TMessage>
{
[JsonProperty("success")]
public bool Success { get; set; }
[JsonProperty("message")]
public TMessage Message { get; set; }
}
The response type for class creation:
public class ClassData : BaseData<string>
{
[JsonProperty("row_index")]
public int RowIndex { get; set; }
}
The response types for user creation:
public class UserData : BaseData<UserMessage>
{
[JsonProperty("user_id")]
public int UserId { get; set; }
}
public class UserMessage
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("message")]
public string Message { get; set; }
}
And finally the overall response types:
public class ClassResponse : Response<ClassData>
{ }
public class UserResponse : Response<UserData>
{ }
And now you can use the objects like you would normally:
var classData = new ClassResponse {Data = new ClassData {Message = ""}};
var classJson = JsonConvert.SerializeObject(classData);
var userData = new UserResponse {Data = new UserData {Message = new UserMessage {Message = ""}}};
var userJson = JsonConvert.SerializeObject(userData);
I will go by the generics route
Let's say that we have a response for Class creation as
public class ClassResponseObject {
public int row_index { get; set; }
public bool success { get; set; }
public string message { get; set; };
}
and for the User creation:
public class UserResponseObject {
public int user_id { get; set; }
public bool success { get; set; }
public MessageResponseObject message { get; set; };
}
and for the Message
public class MessageResponseObject {
public string id { get; set; }
public string message { get; set; };
}
after seeing the above code we are able to find that we have success property common in both the responses, so lets create a base class with that property and inherit these classes with that.
public class BaseResponseObject {
public bool success { get; set; }
}
public class ClassResponseObject : BaseResponseObject {
public int row_index { get; set; }
public string message { get; set; };
}
public class UserResponseObject : BaseResponseObject {
public int user_id { get; set; }
public MessageResponseObject message { get; set; };
}
at this point another common property we see is message, but both have different types. This can be solved with the use of generics. I am considering that there might be more types for the message property of the response, but it should work in either case.
for this let modify our BaseResponseObject and move the message property there
public class BaseResponseObject<TMessage> {
public bool success { get; set; }
public TMessage message { get; set; }
}
so our response objects will become something like this:
public class ClassResponseObject : BaseResponseObject<String> {
public int row_index { get; set; }
}
public class UserResponseObject : BaseResponseObject<MessageResponseObject> {
public int user_id { get; set; }
}
as the last step we need to define the final class for the actual response
public class APIResponse<TResponse> {
public List<TResponse> data { get; set; }
}
now when you are capture the response for the Class creation you can simply capture it in
APIResponse<ClassResponseObject>
similarly for the User creation, capture it in
APIResponse<UserResponseObject>
I hope this helps.
Related
i have a base class which has 2 derived classes and would like to map one field of the output to a "oneof" statement.
public class ClothingCollection
{
public string Name { get; set; }
public string Id { get; set; }
}
public abstract class ClothingCollection<T> : ClothingCollection
{
public abstract string Type { get; }
public abstract List<T> Stuff { get; }
}
public class PantsCollection : ClothingCollection<PantsModel>
{
public override string Type { get; } = "PANTS";
public override List<PantsModel> Stuff { get; }
}
public class ShirtCollection : BaseItem<ShirtModel>
{
public override string Type { get; } = "SHIRT";
public List<BarChartData> Data { get; }
}
public class PantsModel
{
public bool IsJeans { get; set; }
public string owner { get; set; }
}
public class ShirtModel
{
public string Brand { get; set; }
public int Quantity { get; set; }
}
and set the controller response as such ...
[ProducesResponseType(typeof(List<ClothingCollection>), 200)]
[HttpGet("HelloWorld")]
public IActionResult HelloWorld()
{
....
}
and now would like the resulting swagger documentation to be List that looks somewhat like the following (if it's even possible)
{
"Name": "string",
"Id": "string",
"type": "string",
"Stuff":[
anyof -> PantsCollection,
ShirtCollection
]
}
The concept where you have exactly one value of one of several fixed types is called a tagged union, discriminated union, or sometimes a either monad.
You could create a class that has either shirts or pants:
public class ShirtOrPants
{
public ShirtCollection Shirts { get; }
public PantsCollection Pants { get; }
public ShirtOrPants(ShirtCollection shirts, PantsCollection pants)
{
Shirts = shirts;
Pants = pants;
if ((shirts == null) == (pants == null))
{
throw new InvalidOperationException("Must have shirts OR pants!");
}
}
}
There are as far as I know no built in support for tagged unions in c# or any of the more popular serialization languages, so you would need some custom solution. You would typically add various methods to process either shirts or pants depending on which one has a value.
There are generic implementations available, but they might not be very helpful if the goal is to provide readable API definitions.
I am getting tdata from a certain endpoint and the problem id on serialization to my classes. I want to cast the bellow data to my class but cant get how the class should be structured. Check out the data .....
{
"-LYG_AI_oGYjNBrzMlKF": {
"chatDispayText": "",
"chatId": "-LYG_AI_oGYjNBrzMlKF",
"chatName": "",
"chattype": "single",
"imageUrl": "https://wallpaper.wiki/wp-content/uploads/2017/04/wallpaper.wiki-Amazing-celebrities-hd-wallpaper-PIC-WPD004734.jpg",
"lastMessageSent": "aiye",
"lastMessageSentTime": 1549704416263,
"synched": false,
"users": {
"-LYG_AIZ5MvTbjR7DACe": "Uicpm3L15TX0c15pKCI6KUEARyB3",
"-LYG_AI_oGYjNBrzMlKE": "Xsr0z9lsqNOEytX61lJvaGz1A8F2"
}
}
}
If the data you get out the endpoint has a dynamic structure, you can make use of a key-vale pair collection or a dictionary. For instance:
JObject jObject = JObject.Parse(Data); // This would already give you a key-value pair collection
Dictionary<String,Object> collection = new Dictionary<String, Object>();
foreach(var obj in jObject){
collection.Add(obj.Key, obj.Value);
}
However, this isn't a strongly typed approach which means that it is not effective in the majority of scenarios. A better solution when dealing with endpoints would be to define a class with fixed schema, actually something you need in your code, and then map the class to the object yielded by the endpoint using a metadata struct. For example:
public class ChatInfoModel
{
[JsonProperty(Metadata.ChatId)]
public long ChatId { get; set; }
[JsonProperty(Metadata.ChatId, Required = Required.AllowNull)]
public String Message { get; set; }
}
public struct Metadata
{
public const String ChatId = "userChatId";
public const String Message = "messageTxt";
}
And then
var deserializedObject = JsonConvert.DeserializeObject<ChatInfoModel>(data);
However, if your class has the exact same naming convention (but should not necessarily follow the camelCase naming convention) for its properties as in the serialized data, the JsonProperty attribute would not be needed.
You can also deserialize the object without using JsonProperty attribute manually using the first approach, and it is actually advantageous in certain scenarios where your schema comes from a configuration file rather than a struct.
Take inspiration from the Structure below:
public class Rootobject
{
public LYG_AI_Ogyjnbrzmlkf LYG_AI_oGYjNBrzMlKF { get; set; }
}
public class LYG_AI_Ogyjnbrzmlkf
{
public string chatDispayText { get; set; }
public string chatId { get; set; }
public string chatName { get; set; }
public string chattype { get; set; }
public string imageUrl { get; set; }
public string lastMessageSent { get; set; }
public long lastMessageSentTime { get; set; }
public bool synched { get; set; }
public Users users { get; set; }
}
public class Users
{
public string LYG_AIZ5MvTbjR7DACe { get; set; }
public string LYG_AI_oGYjNBrzMlKE { get; set; }
}
I have a json object:
{
"user": {
"id": "xxx"
},
"session": {
"id": "xxx"
}
}
now I need to convert json into a class,
my default answer is to write properties as UserID,sessionID
but I wish something like User.ID & session.ID(which is not possible) from readability point of view.
Make a base class:
public class BaseId //Come up with a better name
{
public string Id { get; set; }
}
Then inherit it from these classes:
public class User : BaseId
{
//Other stuff if you want
}
public class Session : BaseId
{
//Other stuff if you want
}
However you should only do this if User and Session have unique differences from one another (but obviously share the ID property).
If you just want two different variables, then parse them into two different instances of the BaseId class named user and session (obviously no need for the concrete classes this way)
I'm not sure I understand the question entirely. If I were to do this, it would look like this:
public class Foo
{
[JsonProperty("user")]
public User UserIdentity { get; set; }
[JsonProperty("session")]
public Session CurrentSession { get; set; }
}
public class User
{
[JsonProperty("id")]
string Id { get; set; }
}
public class Session
{
[JsonProperty("id")]
string Id { get; set; }
}
You can use JsonProperty class of Newtonsoft.Json
public class Model
{
[JsonProperty(PropertyName = "UserId")]
public int ID { get; set; }
[JsonProperty(PropertyName = "SessionId")]
public int ID1 { get; set; }
}
public class User
{
[JsonProperty("id")]
public string id { get; set; }
}
public class Session
{
[JsonProperty("id")]
public string id { get; set; }
}
public class MyJson
{
[JsonProperty("user")]
private User user { get; set; }
[JsonProperty("session")]
private Session session { get; set; }
public string UserID { get { return user.id; } }
public string SessionID { get { return session.id; } }
}
You Can Write the Model of This type, You can Get the Data As You Request Type of UserID and SessionID.
In Below Sample Code For Testting
var teststring = JsonConvert.DeserializeObject<JObject>("{\"user\": {\"id\": \"xxx\"},\"session\": {\"id\": \"xxx\"}");
var data = JsonConvert.DeserializeObject<MyJson>(teststring.ToString());
var session = data.SessionID;
var userId = data.UserID;
I Was Checked Properly. It Working fine.
I have a JSON API like this,
{
"pokemon": {
"currentPokemon": 1,
"total": 1,
"totalCount": 1,
},
"collections": [
{
"pokemonId": 2310,
"pokemonName": "Pikachu",
"pokemonType": "Land",
"status": {
"Active": "YES",
"Holder": "ASH"
},
"power": {
"Type": 10,
"name": "Thunder"
},
}
]
}
And I have the C# Classes for those API
Public ClassPokemonster
{
public class RootObject
{
[JsonProperty("pokemon")]
public Pokemon Pokemon { get; set; }
[JsonProperty("collections")]
public List<Collection> Collections { get; set; }
}
public class Pokemon
{
[JsonProperty("currentPokemon")]
public int CurrentPokemon { get; set; }
[JsonProperty("total")]
public int Total { get; set; }
[JsonProperty("totalCount")]
public int TotalCount { get; set; }
}
public class Collection
{
[JsonProperty("pokemonId")]
public int PokemonId { get; set; }
[JsonProperty("pokemonName")]
public string PokemonName { get; set; }
[JsonProperty("pokemonType")]
public string PokemonType { get; set; }
[JsonProperty("status")]
public Status Status { get; set; }
[JsonProperty("power")]
public Power Power { get; set; }
}
public class Status
{
[JsonProperty("Active")]
public string Active { get; set; }
[JsonProperty("Holder")]
public string Holder { get; set; }
}
public class Power
{
[JsonProperty("Type")]
public int Type { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
}
}
And I'm trying to assert those values matching the API values using this method
Driver.Instance.Navigate().GoToUrl(url);
//WebRequest
HttpWebRequest getRequest = (HttpWebRequest)WebRequest.Create(url);
getRequest.Method = "GET";
var getResponse = (HttpWebResponse)getRequest.GetResponse();
Stream newStream = getResponse.GetResponseStream();
StreamReader sr = new StreamReader(newStream);
//Deserialize JSON results
var result = sr.ReadToEnd();
Pokemonster deserializedObjects = JsonConvert.DeserializeObject<Pokemonster>(result);
I'm trying to assert in this way,
Assert.Equal("2310", deserializedObject.Collections.PokemonId.ToString());
My assert doesn't fetch the values inside the collections class such as pokemonoId pokemonNameand so on!
Help me getting through this!
The first issue (it's probably just an issue with how you've formatted it here, but I should mention it for completeness) is that you have:
Public ClassPokemonster
But the correct syntax is:
public class Pokemonster
Next, notice that all your other classes are declared inside the class Pokemonster. This kind of structure is called a nested type. The way you've designed it, the Pokemonster class itself contains no properties or methods, but the nested classes Pokemonster.RootObject, Pokemonster.Pokemon, etc. do have properties. So in order to correctly deserialize this type, you have to use:
Pokemonster.RootObject deserializedObjects =
JsonConvert.DeserializeObject<Pokemonster.RootObject>(result);
Finally, note that the property, Pokemonster.RootObject.Collections actually has the type List<Pokemonster.Collection>, but List<T> doesn't have any property named PokemonId (hence the error message). You'll have to access an item in this list to get any of it's properties, like this:
Assert.Equal("2310", deserializedObject.Collections[0].PokemonId.ToString());
I'd like to consume a REST Api and deserialize the nested JSON Response. For that purpose I tried to create some POCO classes which represent the JSON Response [1].
The response looks like this:
{
"success": true,
"message": "OK",
"types":
[
{
"name": "A5EF3-ASR",
"title": "ITIL Foundation Plus Cloud Introduction",
"classroomDeliveryMethod": "Self-paced Virtual Class",
"descriptions": {
"EN": {
"description": "some Text null",
"overview": null,
"abstract": "Some other text",
"prerequisits": null,
"objective": null,
"topic": null
}
},
"lastModified": "2014-10-08T08:37:43Z",
"created": "2014-04-28T11:23:12Z"
},
{
"name": "A4DT3-ASR",
"title": "ITIL Foundation eLearning Course + Exam",
"classroomDeliveryMethod": "Self-paced Virtual Class",
"descriptions": {
"EN": {
"description": "some Text"
(...)
So I created the following POCO classes:
public class Course
{
public bool success { get; set; }
public string Message { get; set; }
public List<CourseTypeContainer> Type { get; set; }
}
/* each Course has n CourseTypes */
public class CourseType
{
public string Name { get; set; }
public string Title { get; set; }
public List<CourseTypeDescriptionContainer> Descriptions { get; set; }
public DateTime LastModified { get; set; }
public DateTime Created { get; set; }
}
public class CourseTypeContainer
{
public CourseType CourseType { get; set; }
}
/* each CourseType has n CourseTypeDescriptions */
public class CourseTypeDescription
{
public string Description { get; set; }
public string Overview { get; set; }
public string Abstract { get; set; }
public string Prerequisits { get; set; }
public string Objective { get; set; }
public string Topic { get; set; }
}
public class CourseTypeDescriptionContainer
{
public CourseTypeDescription CourseTypeDescription { get; set; }
}
And this is the API Code:
var client = new RestClient("https://www.someurl.com");
client.Authenticator = new HttpBasicAuthenticator("user", "password");
var request = new RestRequest();
request.Resource = "api/v1.0/types";
request.Method = Method.GET;
request.RequestFormat = DataFormat.Json;
var response = client.Execute<Course>(request);
EDIT 1: I found a Typo, the Type property in AvnetCourse should be named Types:
public List<AvnetCourseTypeContainer> Type { get; set; } // wrong
public List<AvnetCourseTypeContainer> Types { get; set; } // correct
Now the return values look like:
response.Data.success = true // CORRECT
repsonse.Data.Message = "OK" // CORRECT
response.Data.Types = (Count: 1234); // CORRECT
response.Data.Types[0].AvnetCourseType = null; // NOT CORRECT
EDIT 2: I implemented the Course.Types Property using a List<CourseType> instead of a List<CourseTypeContainer>, as proposed by Jaanus. The same goes for the CourseTypeDescriptionContainer:
public List<CourseTypeContainer> Type { get; set; } // OLD
public List<CourseTypeDescriptionContainer> Descriptions { get; set; } // OLD
public List<CourseType> Type { get; set; } // NEW
public List<CourseTypeDescription> Descriptions { get; set; } // NEW
Now the response.Data.Types finally are properly filled. However, the response.Data.Types.Descriptions are still not properly filled, since there is an additional language layer (e.g. "EN"). How can I solve this, without creating a PACO for each language?
EDIT 3: I had to add an additional CourseTypeDescriptionDetails class, where I would store the descriptive Data. In my CourseTypeDescription I added a property of the Type List for each language. Code Snippet:
public class AvnetCourseType
{
public List<CourseTypeDescription> Descriptions { get; set; }
// other properties
}
public class CourseTypeDescription
{
public List<CourseTypeDescriptionDetails> EN { get; set; } // English
public List<CourseTypeDescriptionDetails> NL { get; set; } // Dutch
}
public class CourseTypeDescriptionDetails
{
public string Description { get; set; }
public string Overview { get; set; }
public string Abstract { get; set; }
public string Prerequisits { get; set; }
public string Objective { get; set; }
public string Topic { get; set; }
}
It works now, but I need to add another property to CourseTypeDescription for each language.
OLD: The return values are
response.Data.success = true // CORRECT
repsonse.Data.Message = "OK" // CORRECT
response.Data.Type = null; // WHY?
So why does my response.Type equal null? What am I doing wrong?
Thank you
Resources:
[1] RestSharp Deserialization with JSON Array
Try using this as POCO:
public class Course
{
public bool success { get; set; }
public string message { get; set; }
public List<CourseTypeContainer> Types { get; set; }
}
Now you have list of CourseTypeContainer.
And CourseTypeContainer is
public class CourseTypeContainer
{
public CourseType CourseType { get; set; }
}
So when you are trying to get response.Data.Types[0].AvnetCourseType , then you need to have field AvnetCourseType inside CourseTypeContainer
Or I think what you want is actually this public List<CourseType> Types { get; set; }, you don't need a container there.
Just in case this helps someone else, I tried everything here and it still didn't work on the current version of RestSharp (106.6.2). RestSharp was completely ignoring the RootElement property as far as I could tell, even though it was at the top level. My workaround was to manually tell it to pull the nested JSON and then convert that. I used JSON.Net to accomplish this.
var response = restClient.Execute<T>(restRequest);
response.Content = JObject.Parse(response.Content)[restRequest.RootElement].ToString();
return new JsonDeserializer().Deserialize<T>(response);
I used http://json2csharp.com/ to create C# classes from JSON.
Then, renamed RootObject to the ClassName of the model file I'm creating
All the data in the nested json was accessible after RestSharp Deserializitaion similar to responseBody.data.Subject.Alias
where data, Subject and Alias are nested nodes inside the response JSON received.