I have a POST endpoint that takes a URL path param and then the body is a list of submitted DTOs.
So right now the request DTO looks something along the lines of:
[Route("/prefix/{Param1}", "POST")]
public class SomeRequest
{
public string Param1 { get; set; }
public List<SomeEntry> Entries { get; set; }
}
public class SomeEntry
{
public int ID { get; set; }
public int Type { get; set; }
public string Value { get; set; }
}
And the service method looks something like:
public class SomeService : Service
{
public SomeResponse Post(SomeRequest request)
{
}
}
If encoded via JSON, the client would have to encode the POST body this way:
{
"Entries":
[
{
"id": 1
"type": 42
"value": "Y"
},
...
]
}
This is redundant, I would like the client to submit the data like this:
[
{
"id": 1
"type": 42
"value": "Y"
},
...
]
Which would have been the case if my request DTO was simply List<SomeEntry>
My questions is: is there a way to "flatten" the request this way? Or designate one property of the request as the root of the message body? i.e perhaps:
[Route("/prefix/{Param1}", "POST")]
public class SomeRequest
{
public string Param1 { get; set; }
[MessageBody]
public List<SomeEntry> Entries { get; set; }
}
Is this doable in any way in ServiceStack?
I was able to sort of get this to sort of work by subclassing List<T>:
[Route("/prefix/{Param1}", "POST")]
public class SomeRequest : List<SomeEntry>
{
public string Param1 { get; set; }
}
Then you can send a request like this:
POST /prefix/someParameterValue
Content-Type: application/json
[ { "ID": 1, "Type": 2, "Value": "X" }, ... ]
But if you have any choice in the design, I wouldn't recommend this. Here's a couple of reasons to start with:
I found at least one problem with this at runtime: sending an empty array, e.g. [ ] in JSON, is resulting in a 400 status code with RequestBindingException
It's less flexible. What if you do need to add additional top-level properties to the request in the future? You would be stuck with them being path/query params. Having a regular class-containing-a-list allows you to add new optional properties at the top level of the request body, with backward compatibility
OK I've managed to achieve this. Not the prettiest solution but will do for now.
I wrapped the content type filter for JSON:
var serz = ContentTypeFilters.GetResponseSerializer("application/json");
var deserz = ContentTypeFilters.GetStreamDeserializer("application/json");
ContentTypeFilters.Register("application/json", serz, (type, stream) => MessageBodyPropertyFilter(type, stream, deserz));
Then the custom deserializer looks like this:
private object MessageBodyPropertyFilter(Type type, Stream stream, StreamDeserializerDelegate original)
{
PropertyInfo prop;
if (_messageBodyPropertyMap.TryGetValue(type, out prop))
{
var requestDto = type.CreateInstance();
prop.SetValue(requestDto, original(prop.PropertyType, stream), null);
return requestDto;
}
else
{
return original(type, stream);
}
}
_messageBodyPropertyMap is populated after init by scanning the request DTOs and looking for a certain attribute, as in the example in my original question.
Related
I have to manage a lot of API calls to a service and all the response messages have a common structure except the "data" field which varies according to the endpoint called and whether the call was successful or not.
Looking for a way to smartly manage the whole situation I produced a satisfying solution with the use of generics and Mapster.
This is a typical response message:
{
"type": "send",
"datetime": "2022-02-21",
"correlation_id": "dc659b16-0781-4e32-ae0d-fbe737ff3215",
"data": {
"id": 22,
"description": "blue t-shirt with stripes",
"category": "t-shirt",
"size": "XL"
}
}
The data field is totally variable, sometimes it is a one-level structure, sometimes a multi-level structure, sometimes an array, sometimes a simple string and sometimes a null.
Clearly the response messages are known and depends on the called endpoint so I know what to expect when I make the call but there is a case where the structure can still change.
If the call is unsuccessful and there is an error, a 200 is still returned from the endpoint but the response is like this:
{
"type": "error",
"datetime": "2022-02-21",
"correlation_id": "dc659b16-0781-4e32-ae0d-fbe737ff3215",
"data": {
"id": 1522,
"description": "product code not found",
}
}
Looking for an elegant and concise solution to manage all cases with a single method, I was able to find a solution:
These are my models:
public class Response<T> where T : class
{
public string type { get; set; }
public T data { get; set; } = null;
public Error Error { get; set; } = null;
public bool IsError => Error != null;
public string ErrorMessage => IsError ? $"An error occurred. Error code {Error.id} - {Error.description}" : "";
}
public class Customer
{
public int customerId { get; set; }
public string name { get; set; }
}
public class Supplier
{
public int supplierId { get; set; }
public string company { get; set; }
}
public class Error
{
public int id { get; set; }
public string description { get; set; }
}
And this is my function that manage all the deserializations:
private static Response<T> GetData<T>(string json) where T : class
{
//Deserialize the json using dynamic as T so can receive any kind of data structure
var resp = JsonConvert.DeserializeObject<Response<dynamic>>(json);
var ret = resp.Adapt<Response<T>>();
if (resp.type == "error")
{
//Adapt the dynamic to Error property
ret.Error = ((object)resp.data).Adapt<Error>();
ret.data = null;
}
return ret;
}
So I call my function in this way:
var customerData = GetData<Customer>("{\"type\":\"send\", \"data\": {\"id\":1, \"name\": \"John Ross\"}}");
if (customerData.IsError)
Console.WriteLine($"ERROR! {customerData.ErrorMessage}");
else
Console.WriteLine($"The response is OK. Customer name is {customerData.data.name}");
As you can see, the solution adopted is elegant and works very well.
The only problem I haven't found solution is that Mapster.Adapt doesn't fail if I try to fit the wrong json to type T.
So if I deserialize the json of the customer in the supplier class I would not notice the problem.
Is there a way in Mapster to know if the object I'm trying to adapt isn't compatible with the target type?
So I could raise an exception and my procedure would be perfect.
Here is a repo with working example
https://github.com/mmassari/MapDynamicWIthMapster
Thank you
The solution you are trying to work out might seem elegant, but in reality you could be introducing a code smell which might create hard to catch bugs in the future.
You might want to reconsider using the knowledge you already have about the endpoints to deserialize the response to their correct response types. This is good practice, and it is simple to understand and maintain for both yourself and future developers.
With regards to the problematic "product code not found" responses, you could add a validation step that deserializes the JSON response to an object of type JObject which you can safely query to check for existence of known error messages.
Here is a simple example:
private static void ValidateResponse(string json)
{
var knownErrors = new List<string>()
{
"Product code not found",
"Customer does not exist",
"Other known errors here"
};
var jobj = JsonConvert.DeserializeObject<JObject>(json);
var error = jobj.SelectToken("data.description");
if (error != null)
{
var errorMessage = error.Value<string>();
if (knownErrors.Contains(errorMessage))
{
throw new ApplicationException(errorMessage);
}
}
}
You may want to consider trimming and normalizing casing to avoid getting thrown off by small variations in the error messages.
I've been trying to deserialize the following JSON string into a model using Newtonsoft's Json library, but failing to do so. The json string is as follows:-
[
[
{
"events": [
{
"d": "Dec 2019 Final",
"e": "Released 5 Mar 2020"
}
]
}
],
[
{
"events": [
{
"d": "Some String",
"e": "Some Other string"
}
]
}
]
]
I tried doing so by creating its model using Visual Studio internal tool for json to C# (paste special). Also tried creating the models using QuickTypes.io, but so far nothing worked.
The best I can do is parse the api response as List<List<object>> and then manually get the values by iterating its keys & values, which is something I totally want to avoid.
Is there a better way to handle this data?
I hope I understand your question correctly.If you want to stick to Anonymous types, you could make use of JsonConvert.DeserializeAnonymousType for the purpose.
var innerAnonymousType = new {d=string.Empty,e=string.Empty};
var innerAnnonymousTypeList = CreateList(innerAnonymousType);
var outerAnonymousType = CreateList(new {events = innerAnnonymousTypeList});
var result = JsonConvert.DeserializeAnonymousType(json, CreateList(outerAnonymousType));
Where CreateList is defined as
public static List<T> CreateList<T>(params T[] elements)
{
return new List<T>(elements);
}
Using JsonConvert.DeserializeAnonymousType
Output Sample
You can use a base model and parse your json:
public class BaseClass
{
public List<EventModel> Events { get; set; }
}
public class EventModel
{
[JsonProperty(PropertyName = "d")]
public string Date { get; set; }
[JsonProperty(PropertyName = "e")]
public string Event { get; set; }
}
And for deserialize json to model use this:
var model = JsonConvert.DeserializeObject<List<List<BaseClass>>>("your json");
I am writing an application where I do need to handle some scenarios beforehand in my controller class like certain property must have been provided otherwise status code would be BadRequest. Here is my class lookalike.
public class MyClass
{
[Required]
public IEnumerable<NewObject> NewObjects { get; set; }
}
public class NewObject : INewObject
{
public NewObject(string typeName, IEnumerable<Property> properties)
{
TypeName = typeName;
Properties = properties;
}
[JsonProperty(Required = Required.Always)]
public string TypeName { get; }
public IEnumerable<IProperty> Properties { get; }
}
public interface IProperty
{
string Name { get; }
object Value { get; }
}
Now though I have marked TypeName as required property and if I do not pass that in json content while sending request from swagger, json deserialization doesn't fail. I tried to search but I got an answer that setting Required to Always should work.
Below is the Json Content I am passing through swagger:
{
"NewObjects": [
{
"Properties": [
{
"Name": "string",
"Value": ''
}
]
}
]
}
I wrote below piece of code too by looking at one of the solution:
var config = new HttpConfiguration();
var jsonFormatter = config.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Error;
config.MapHttpAttributeRoutes();
Still it's not working:
Note: I am using Newtonsoft.Json version 11.0.1
This seems to be swagger issue because when I serialize input C# object and when again deserialize it, I am getting proper error.
For example in my controller class if I say:
var input2 = JsonConvert.DeserializeObject<MyClass>(JsonConvert.SerializeObject(input))
Then input2 throws an exception.
You can take a look at FluentValidation. If I am not mistaken it is designed to validate data in jsons forms specifically.
using FluentValidation;
public CertainActionValidator()
{
RuleFor(x => x.PropertyName).NotEmpty()
}
You can add plenty of additional conditions in there.
This is the JSON object I am receiving from a GET request:
{
"data": {
"valve_maker": [],
"water_volume": [
"15L",
"20L",
"..."
],
"cylinder_manufacturer": [
"Tianhai"
],
"qc_stamp": [
"TS"
],
"reference_standard": [
"GB 5099"
],
"production_licence": [
"TS2210752-2016"
],
"valve_production_licence": [
"TSF210030"
],
"rate_of_residual_deformation": {
"1": "<3%",
"2": "<10%"
},
"material_number": {
"1": "30CrMo",
"2": "34CrMo4",
"3": "..."
},
"heat_treatment": {
"1": "...",
"2": "..."
},
"drawing_number": {
"1": "...",
"2": "..."
},
"cylinder_thickness": []
}
right now, I am able to parse JSON objects with a simpler structure like :
{
"data": [
{
"gas_id": "ID of the gas",
"gas_name": "Gas name"
}
]
by using something like this:
private void jsonparsegas(string res)
{
JObject par = JObject.Parse(res);
foreach (JToken data in par["data"].Children())
{
string id = data["gas_id"].ToString();
string name = data["gas_name"].ToString();
if (this.cmbCylType.Items.Contains(name) == false)
{
this.cmbCylType.Items.Add(name);
}
}
}
When I try to apply the same thing to the more complicated JSON object, I get an error:
private void jsonparsecoc(string res)
{
//JObject par = JObject.Parse(res);
var jObj = (JObject)Newtonsoft.Json.JsonConvert.DeserializeObject(res);
foreach (var child in jObj["data"].Children())
{
string vMaker = child["valve_maker"].ToString(); //error thrown here right away
string wVolume = child["water_volume"].ToString();
string cMan = child["cylinder_manufacturer"].ToString();
string QC = child["qc_stamp"].ToString();
string rStandard = child["reference_standard"].ToString();
string pLicence = child["production_licence"].ToString();
string VPL = child["valve_production_licence"].ToString();
string rrd = child["rate_of_residual_deformation"].ToString();
string mNum = child["material_number"].ToString();
string hTreatment = child["heat_treatment"].ToString();
string dNum = child["drawing_number"].ToString();
string cThick = child["cylinder_thickness"].ToString();
}
Cannot access child value on Newtonsoft.Json.Linq.JProperty
I have tried a few different things I found on StackOverflow, but I don't really understand how Deserializing of the objects works. The simpler parsing works just fine, and allows me to add all "gas_name"s that I receive from my GET request to a combobox. The format the first "valve_maker" child of "data" seems to have the same structure as "gas_id" or "gas_name" in the more similar JSON object, but this is where I receive the error right away. If I had to guess at a cause for the error, I'd say it has something to do with the difference between using
"valve_maker": []
and using
"gas_id": "ID of the gas"
in the objects. also I notice "data" is followed by [] brackets in the simpler one, and {} in the more complicated one.
If anyone could link to some good reading material, or offer a good explanation of a solution/what's going on, I'd really appreciate it.
This part is the key to the problem you're having:
in the objects. also I notice "data" is followed by [] brackets in the simpler one, and {} in the more complicated one.
In JSON,
[] brackets enclose an array
{} brackets enclose an object
In both code examples, you are digging into the object by looping through the results using par["data"].Children(). To be consistent with the JSON model, JSON.NET defines different behavior for resolving children of objects and arrays. The children of an object are its properties and the children of an array are its items.
In your code, the input to jsonparsegas is an array of simple objects with 2 properties where the input to jsonparsecoc is a single complex objects with many properties.
In jsonparsegas, the Children() call gives you an array of all the simple gas objects. You loop through these objects and extract the values of "gas_id" and "gas_name" for each object. In your example data, there only happens to be one gas object so your code only executes once.
In jsonparsecoc, the Children() call actually gives you property values for the properties of the complex object, since the result is an object and not an array. So, when you loop through this result you can't access things like "valve_maker", because they are defined on the complex object and you have already stepped into the value for valve_maker then this executes.
The solution is simple. Don't loop through the properties in jsonparsecoc. Instead of foreach(var child in jObj["data"].Children()) you need something like var child = jObj["data"];. This will give you the reference to the object that actually contains each of the properties you are trying to access.
#smartcaveman did a good job of explaining what is going wrong with your code. However, you might find your data a little easier to process if you defined strongly-typed classes for it like this:
class RootObject
{
public Data Data { get; set; }
}
class Data
{
[JsonProperty("valve_maker")]
public List<string> ValveMaker { get; set; }
[JsonProperty("water_volume")]
public List<string> WaterVolume { get; set; }
[JsonProperty("cylinder_manufacturer")]
public List<string> CylinderManufacturer { get; set; }
[JsonProperty("qc_stamp")]
public List<string> QCStamp { get; set; }
[JsonProperty("reference_standard")]
public List<string> ReferenceStandard { get; set; }
[JsonProperty("production_licence")]
public List<string> ProductionLicense { get; set; }
[JsonProperty("valve_production_licence")]
public List<string> ValveProductionLicense { get; set; }
[JsonProperty("rate_of_residual_deformation")]
public Dictionary<string, string> RateOfResidualDeformation { get; set; }
[JsonProperty("material_number")]
public Dictionary<string, string> MaterialNumber { get; set; }
[JsonProperty("heat_treatment")]
public Dictionary<string, string> HeatTreatment { get; set; }
[JsonProperty("drawing_number")]
public Dictionary<string, string> DrawingNumber { get; set; }
[JsonProperty("cylinder_thickness")]
public List<string> CylinderThickness { get; set; }
}
You can deserialize the JSON to your classes like this:
RootObject obj = JsonConvert.DeserializeObject<RootObject>(json);
Demo here: https://dotnetfiddle.net/p0D7ze
public class Test{
public Data data{get;set;}
}
public class Data{
public List<string> Value_maker{get;set;}
public List<String> Water_Volume{get;set;}
public List<String> cylinder_manufacturer{get;set;}
}
Create Class structure Like that and after that use to deserialize
Jsonconvert.DeserializeObject(JsonString)
it will convert json into proper object, keep in mind structure should be proper and property name should be same as your json property
Hope It will Help you
I'm trying to serialize an object in a different way that the default serialization does.
I have a list of objects like this:
public class PausesByAgentModel
{
public PausesByAgentModel()
{
this.Pauses = new Dictionary<string, string>();
}
[DisplayName( "Agent" )]
public string AgentFullName { get; set; }
public Dictionary<string, string> Pauses { get; set; }
}
This object contains the different pauses for a Agent an the time for each pause in a dictionary because the pauses reasons are are dynamics (Key: Pause name, Value: Time in string format).
If I serialize this object as JsonResult to return it in my controller method, I get the following json:
{
"data" : [
{
"AgentFullName" : "John Doe",
[
{ "Break": "00:15:31" },
{ "Launch" : "01:01:23" },
{ "Mail" : "00:05:12" }
]
} ]
}
But I need that the pauses were placed like properties of the same object like this one:
{
"data" : [
{
"AgentFullName" : "John Doe",
"Break": "00:15:31",
"Launch" : "01:01:23",
"Mail" : "00:05:12"
} ]
}
I can't change the class design. So, I need to serialize the object in a different way as default design.
There is a way to merge or serialize the object like the second example?
Thanks.
As #qamar says on the comments, use a dynamic type.
public List<dynamic> ParseData(PausesByAgentModel model){
List<dynamic> result = new List<dynamic>();
foreach(String key in model.Keys){
result.Add(new { pauses = key,value = model[key] });
}
return result;
}