JSON Polymorphic serialization in .NET7 Web API - c#

.NET7 Includes a lot of improvements for System.Text.Json serializer, one of which is polymorphic serialization of types using the new [JsonPolymorphic] attribute. I am trying to use it in my Asp.Net web API, however it doesn't seem to serialize the type discriminator despite the fact that the model is properly setup.
It only happens when trying to send the objects over the wire, when using JsonSerializer, everything appears to be working well. For example:
// This is my data model
[JsonPolymorphic]
[JsonDerivedType(typeof(SuccesfulResult), typeDiscriminator: "ok")]
[JsonDerivedType(typeof(ErrorResult), typeDiscriminator: "fail")]
public abstract record Result;
public record SuccesfulResult : Result;
public record ErrorResult(string Error) : Result;
// Some test code that actually works
var testData = new Result[]
{
new SuccesfulResult(),
new ErrorResult("Whoops...")
};
var serialized = JsonSerializer.SerializeToDocument(testData);
// Serialized string looks like this:
// [{ "$type": "ok" }, { "$type": "fail", "error": "Whoops..." }]
// So type discriminators are in place and all is well
var deserialized = serialized.Deserialize<IEnumerable<Result>>()!;
// This assertion passes succesfully!
// We deserialized a collection polymorphically and didn't lose any type information.
testData.ShouldDeepEqual(deserialized);
// However, when sending the object as a response in an API, the AspNet serializer
// seems to be ignoring the attributes:
[HttpGet("ok")]
public Result GetSuccesfulResult() => new SuccesfulResult();
[HttpGet("fail")]
public Result GetFailResult() => new ErrorResult("Whoops...");
Neither of these responses are annotated with a type discriminator
and my strongly-typed clients can't deserialize the results into a proper hierarchy.
GET /api/ok HTTP/1.1
# =>
# ACTUAL: {}
# EXPECTED: { "$type": "ok" }
GET /api/fail HTTP/1.1
# =>
# ACTUAL: { "error": "Whoops..." }
# EXPECTED: { "$type": "fail", "error": "Whoops..." }
Am I missing some sort of API configuration that would make controllers serialize results in a polymorphic manner?

Specifying [JsonDerivedType(...)] on individual subclasses and on the base type seems to resolve an issue but barely seems intentional. This possibly might be fixed in future releases.
[JsonPolymorphic]
[JsonDerivedType(typeof(SuccesfulResult), typeDiscriminator: "ok")]
[JsonDerivedType(typeof(ErrorResult), typeDiscriminator: "fail")]
public abstract record Result;
[JsonDerivedType(typeof(SuccesfulResult), typeDiscriminator: "ok")]
public record SuccesfulResult : Result;
[JsonDerivedType(typeof(ErrorResult), typeDiscriminator: "fail")]
public record ErrorResult(string Error) : Result;

Related

.net core WebAPI: get rid of "/swagger/v1/swagger.json" for $ref properties in SwaggerExample in case of returning JSON-Schema value

Swagger somehow adds some stuff I like to be removed. All examples shown are simplified.
I have a WebAPI with a controller operation which returns JSON-Schemas. It looks like this:
[SwaggerResponseExample((int)HttpStatusCode.OK, typeof(ServiceDemandContractExamplesProvider))]
public IActionResult GetContract(string serviceName)
{
return Content(jsonSchemaAsString, "application/json")
}
Then I have a Swagger Example for that Operation looking like this:
public class ServiceDemandContractExamplesProvider : IMultipleExamplesProvider<JsonDocument>
{
public IEnumerable<SwaggerExample<JsonDocument>> GetExamples()
{
JsonDocument jsonDocument;
// Build a JsonDocument which represents a JSON schema.
// ....
// check the JSON-String outcome with debugger:
// The $ref properties inside the schema points to the correct position in the schema itself,
// no '/swagger/v1/swagger.json' content so far
yield return SwaggerExample.Create("Example1", jsonDocument);
}
}
When I now create an example like this (simplified):
The SwaggerUI shows me the example like that:
How can I get rid of that /swagger/v1/swagger.json in the $ref properties?

Deserializing an api call response content without having to create a model representing the content

Is there another way to deserialize the response content of an api call into a generic object in .Net without having to create a model representing the json object? In other words, is there or does .Net provide a generic object or model that i can use to deserialize the response into. The reason being is because every api call response is not the same which means i have to create a new model for every api call. And i have about 20 different endpoints that return different responses, which means i would have to create 20 models representing the response.
My recommendation would be to create request and response models for this, even though it feels like extra work. This is because you'll need to eventually pass the parameters to functions in your business layer anyway, and you'll want to take advantage of the type safety without doing a bunch of Int32.TryParse's, which at that point, you're creating variables and doing extra work anyway. Actually, you're not going to be able to outrun the type safety the language not only provides, but generally requires. What I've done, is just copy/paste my DTO or EDMX table model into the new class, then decorate it. Pretty fast.
You may be looking for dynamic object which is late bound with unknown compile time properties.
A handy implementation of this is the JObject available with Newtonsoft.Json package and it offers similar functionality to the typeless elements of Javascript in that the properties can be of any type, with any nesting, etc. as long as it was parsed from well formed Json.
Usage is super simple, but watch out for null values, etc as default behavior for dynamic (aka ExpandoObject under the hood) is to throw exceptions for properties (aka Keys) not found...
public static void Run()
{
string apiJsonResponse = #"{
Name: 'Luke Skywalker',
Title: 'Jedi',
Skills : [
'Lightsaber',
'The Force'
]
}";
dynamic json = JObject.Parse(apiJsonResponse);
Console.WriteLine($"Name: {json.Name}");
Console.WriteLine($"Title: {json.Name}");
Console.WriteLine($"Skills: {String.Join(", ", json.Skills)}");
}
The result will be the Json dynamically parsed and rendered without any strongly typed model:
You can use generic method to have list of JObject. Based on your need you can extract model from jobject.
private static async Task<List<JObject>> CallApi(Uri uri, HttpContent data = null, string headerKey = null, string headerKeyVal = null)
{
string res = string.Empty;
try
{
var handler = new HttpClientHandler
{
//UseDefaultCredentials = true,
};
using var client = new HttpClient(handler);
if (!string.IsNullOrWhiteSpace(headerKey))
client.DefaultRequestHeaders.Add(headerKey, headerKeyVal);
var post = await client.GetAsync(uri);
//var post = await client.PostAsync(uri);
if (post.StatusCode != HttpStatusCode.InternalServerError)
{
res = await post.Content.ReadAsStringAsync();
}
return JsonConvert.DeserializeObject<List<JObject>>(res);
}
catch (Exception ex)
{
throw ex;
}
}
in .Net 3.1 after version, can use using System.Text.Json
api response data
{
"Status" : "0"
"Data" : {
"FirstName" : "firstName",
"LastName" : "lastName"
}
}
Response Model
public class ResponseModel
{
public string Status { get; set; }
public JsonElement Data { get; set; }
}
Deserialize
public void Deserialize()
{
var jsonString = "{ \"Status\": \"0\", \"Data\": { \"FirstName\": \"firstName\", \"LastName\" : \"last_name\"} }";
var response = JsonSerializer.Deserialize<ResponseModel>(jsonString);
//get first_name
var firstName = response.Data.GetProperty("FirstName").GetString();
Console.WriteLine(firstName);
}

Is it possible to use OData on a wrapped json response of an ASP.NET Core WebAPI endpoint by just adding [EnableQuery] attribute to controller methods

Sorry I know there is a similar question that was asked here Wrapping a Web API response in JSON but still having it work with IQueryable and oData but I'd like to ask if it's possible to do the similar without having it to rewrap the result?
I have a big number of existing ASP.NET Core (3.1) WebAPI endpoints that are returning a response type which, beside the data I'd like to apply the OData filters to, always includes additional base properties for errors and a completion indicator that are all serialized to json.
I'd like to have those WebAPI endpoints become OData as well, or in case no filter is appended as part of the querystring just return the result as it was/is...i.e. if ?$filter=value eq 'Y' is appended to an existing WebAPI endpoint URL it should apply that OData filter just to the data contained within the json response (thus not having to change the OData filter to contain the adjusted path for data or if say $top=N is applied it would return the top N items in the data array), so for a regular response like this:
{"complete":true,"data":[{"id":1,"value":"X"},{"id":2,"value":"Y"},{"id":3,"value":"Z"}],"errors":[]}
it would return just the middle element in the data array but keep the surrounding json properties like so:
{"complete":true,"data":[{"id":2,"value":"Y"}],"errors":[]} (this is returning a DataResponse< IdValue > in a WebAPI controller for which I've found a partial solution described bellow, but unfortunately since DataResponse is generic I'd have to add all of these NewtonSoft JsonConverter to the startup which seems like a bad solution to me so I'd like the solution to work for any passed in type beside simplistic IdValue and preferably even for OData endpoints i.e. same endpoints but with $top/$skip/$filter passed in the end of querystring, for any class which is serializable to JSON of course...).
Trying to do that what I've managed so far was to change that generic DataResponse type in a way that it also implements IQueryable interface and had its implementation Expression, ElementType and Provider be returned as those of the contained data property which is now of type IEnumerable like so:
public class DataResponse<T> : BaseResponse, IQueryable<T>
{
// BaseResponse holds the errors and complete indicator properties and the DataReponse has the data property bellow like so:
public IEnumerable<T> Data { get; set; }
// I just implemented tehe IQueryable interface like so:
public Expression Expression
{
get
{
return Data.AsQueryable().Expression;
}
}
public Type ElementType
{
get
{
return Data.AsQueryable().ElementType;
}
}
public IQueryProvider Provider
{
get
{
return Data.AsQueryable().Provider;
}
}
public IEnumerator<T> GetEnumerator()
{
Items = (IEnumerable<T>)this.Provider.Execute(this.Expression);
return this.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
if (typeof(Expression) == typeof(System.Linq.EnumerableQuery))
{
// Checking the expression since in case of no OData querystring just this would fail
return ((IEnumerable)this.Provider.Execute(this.Expression)).GetEnumerator();
}
else
{
return Data.GetEnumerator();
}
}
}
}
alongside a NewtonSoft JsonConverter (which was a must for using OData on Endpoints with WebAPI controller writen using ASP.NET Core 3.1 as per numerous articles I've read):
public class DataResponseJsonConverter<T> : JsonConverter<DataResponse<T>> {
public override void WriteJson(JsonWriter writer, DataResponse<T> value, JsonSerializer serializer) {
JToken t; t = JToken.FromObject(new {
Data = value.Data,
Complete = value.Complete
}); }
JObject o = (JObject)t;
o.WriteTo(writer);
}
public override DataResponse<T> ReadJson(JsonReader reader, Type objectType, DataResponse<T> existingValue, bool hasExistingValue, JsonSerializer serializer) {
throw new NotImplementedException();
}
}
which was added to the Configure method of the Startup.cs class like so (whole file omitted for brevity):
services.AddControllers().AddNewtonsoftJson(options => {
options.SerializerSettings.Converters.Add(new DataResponseJsonConverter<IdValue>());
});
but the code above only works for the IdValue I added in the NetwonsoftJson options and not all and every class my controllers could be returning (I was hoping I could add a generic DataResponseJsonConverter< object >() and I've even added the JsonConverter attribute to the DataResponse class without the inner generic type class but both of those fail, although it uses that converter I wrote above but without a concrete class being added into options for each dataresponse class I'm guessing it fails because it can't figure out how to serialize any of those other DataResponse< not_specified_classes_in_startup > return types).
So although managing to apply the OData filters successfully now with the attempts described the code above still just returns the Data portion of the response instead of the whole wrapped DataResponse including the errors (which I can't set as they aren't settable in the BaseResponse) and even that completion indicator with passing in an OData endpoint, and me trying to change the IEnumerable.GetEnumerator() to new up and/or rewrap the response in that single place there just causes a stack overflow error (since I suppose OData serialized the response using the GetEnumerator call and that would just keep calling itself if I were to return the recreate and return an enumerator of the same typed object there...maybe, I'm thinking ).
Is there a way to do this in ideally just this one place thus having it work with just decorating the controller method with the [EnableQuery] and nothing else in the controller?
Can it be done that way instead of having to rewrite each and every controller and apply the ODataQueryOptions there (which I'm not even sure can be done easily in my case given the errors are filled elsewhere and completion indicator part of the response even has no public setter) and/or do I have to do something even more complex (I think I saw there's maybe somthing like a QueryTranslator also but not sure if I can use that) and how please? I'm just getting into the IQueryable so if this is a simple thing and I missed something I apologize. TIA

NEST : Issue in getting data from search response (ISearchResponse) in ElasticSearch

I wrote a C# code using NEST, which makes search queries to my ES database. I can see these queries succeed and give a json response body through Postman. I want to use these responses in my code. For example,
ISearchResponse<class> myquery = client.Search<class>(...)
(some successful api call)
The response body is something like:
{
"took": 5,
...
...
"hits": {
"max_score": 1.2,
"hits": [
{
"_index": "sample",
...
"_source": {
"name": "generic name",
"profession": "lawyer",
...
}
}
]
}
"aggs" : {
...
}
}
I can get the "took" value here by doing myquery.Took. Similarly I can see the definition of ISearchResponse<> contains data members for MaxScore, TimedOut etc.
My question is, In the same way if I want to get the value of, say the name field or some bucket in aggr, to use in my code. How can I do this? Please Help.
Note :
The Documentation only explained how to handle error responses and I can see in the debugger that probably .Documents is storing this somehow, but I'm not able to retrieve the data (or probably I can't understand how to). So please also explain how to get it from .Documents if that is the case.
The "_source" JSON object of each hit will be deserialized into the type T specified as the generic type parameter on Search<T>. In your example, "_source" will be deserialized into class, so simply define properties on class for the properties on "_source". For example
public class MyDocument
{
[PropertyName(Name = "name")]
public string Name {get;set;}
[PropertyName(Name = "profession")]
public string Profession {get;set;}
}
var response = client.Search<MyDocument>();

How to Deserialise JSON Data to Array of Model objects?

I have a C#/ASP.Net project using WebAPI2 that has an API endpoint. That endpoint provides an array of JSON objects as a response to a GET request. I use a Model of the native object for serialisation, which is the standard way in WebAPI2 as I understand it.
The serialization happens 'behind the scenes' when I send an OK HttpActionResult typed for an IEnumerable of models,
return Ok<IEnumerable<Model>>(arrayOfModels);
I have a second app that receives the JSON string representation of the array of models. The inbound string to deserialise looks like this, for an array with a single element...
[
{
"ExecID": "EXWB4KT-1",
"Symbol": "CERT-EUR-TEST",
"ExecutionTime": "2016-07-28T14:59:56.24",
"BuyExchID": "IDH64KT-1",
"SellExchID": "IDH64KT-3",
"Tradable": {
"CERT": {
"AccSrc": "ANDY-CERT",
"AccDst": "HIBLET-CERT",
"Qty": "0.01000000",
"State": "PENDING"
},
"EUR1": {
"AccSrc": "HIBLET-EUR",
"AccDst": "ANDY-EUR",
"Qty": "0.33",
"State": "PENDING"
}
}
}
]
How should I rehydrate the JSON data, bearing in mind that I have the WebAPI2 model available in another project?
As I have control of both ends, I am thinking the deserialisation back into an array of models should be trivial, but I am not sure how to do it. I know there are lots of possible ways to do this, I am looking for the sanest or cleanest way.
Try the Newsoft.Json nuget package. Then it's a matter of creating your model and deserializing. Something like this:
public class MyObject
{
int ExecID { get; set; }
string Symbol { get; set; }
//etc etc
}
Then in your method:
using Newtonsoft.Json;
public class MyMethod(string json)
{
List<MyObject> objList = JsonConvert.DeserializeObject<List<MyObject>>(json);
}
This code may not be exact, but should be something similar

Categories