Cannot deserialize the current JSON array - Auth0 Management API Endpoint - despite using online POCO generators - json.net

I am working with Auth0 Management API endpoints, the issue with this one.
https://auth0.com/docs/api/management/v2?_ga=2.197148647.957265821.1601726170-1678439180.1588036522#!/Users/get_users
Here is my rest code.
var client = new RestClient(tempapiendpoint);
var request = new RestRequest(Method.GET);
request.AddHeader(header, bearerstring);
request.AddParameter(specificfieldname,specificfields);
request.AddParameter(includefieldname, includetrueorfalse);
IRestResponse response = await client.ExecuteAsync(request);
var myDeserializedClass = JsonConvert.DeserializeObject<Root>(response.Content);
I have the following response.
[
{
"email": "somevalue",
"name": "somevalue",
"nickname": "somevalue",
"user_id": "somevalue"
},
{
"email": "somevalue",
"name": "somevalue",
"nickname": "somevalue",
"user_id": "somevalue"
},
{
"email": "somevalue",
"name": "somevalue",
"nickname": "somevalue",
"user_id": "somevalue"
}
]
At this point, I use an online class generator, such as, https://json2csharp.com/
// Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(myJsonResponse);
public class MyArray {
[JsonProperty("email")]
public string Email;
[JsonProperty("name")]
public string Name;
[JsonProperty("nickname")]
public string Nickname;
[JsonProperty("user_id")]
public string UserId;
}
public class Root {
[JsonProperty("MyArray")]
public List<MyArray> MyArray;
}
and everytime, I get the same error.
Newtonsoft.Json.JsonSerializationException: Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'RandomStuffGeneratorPrivate.POCO.Root' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly.
To fix this error either change the JSON to a JSON object (e.g. {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List<T> that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array.
Path '', line 1, position 1.
A few more things I have tried.
I have tried another source for class generation, https://app.quicktype.io/. I get the exact same error.
I have checked the json for validity. it is in the correct format.
even if hand map it, this is a straight forward json.
Further, I noticed that the name of the user class and the collection name are both the same. So, I changed the name for the user class. (it never gave any errors while debugging but I changed it anyway). no change in error
When generating classes, I am taking the json string directly from the live response during debugging, from IRestResponse response.content, just in case the online API documentation is making a mistake.
i have looked at other stack questions, and in those cases, I noticed that there was some mistake related to not having a list. Here, I definitely have mapped (the online generators wont make such a mistake) the returning array to a list.

Looks to me that you are deserializing to the wrong class, try using : JsonConvert.DeserializeObject<MyArray[]>(myJsonResponse)

(looks like Thomas has already provided a solution which i also discovered on my own. Posting some additional details here.)
Ultimately, this seems to have worked for me.
var myDeserializedClass = JsonConvert.DeserializeObject<List<SingleUser>>(response.Content);
I think, the issue was, I was looking for a solution, that would me something like this.
myDeserializedClass
and, I could use it, like this,
var nameOfPerson = myDeserializedClass.MyArray[0].Name;
But, i think that is not how JSON works. Perhaps, if there were other fields in the JSON body, with a mix and match of collection and non-collection values. Since, this was a complete collection, the outer layer gets eliminated or something.
Further, Quicktype class generator clearly says this at the top.
// <auto-generated />
//
// To parse this JSON data, add NuGet 'Newtonsoft.Json' then do:
//
// using RandomStuffGeneratorPrivate.POCO;
//
// var allUsers445 = AllUsers445.FromJson(jsonString);
and, the function is defined like this.
public static List<AllUsers445> FromJson(string json) => JsonConvert.DeserializeObject<List<AllUsers445>>(json, RandomStuffGeneratorPrivate.POCO.Converter.Settings);

Related

How can I use a default value/model on WebAPI EmptyBody?

I have dotnet WebAPI and I'm trying to get a specific behaviour but am constantly getting 415 responses.
I have reproduced this by starting a new webapi project using dotnet new webapi on the command line. From there, I added two things: a new controller, and a model class. In my real project the model class is obviously a bit more complex, with inheritance and methods etc...
Here they are:
[HttpGet("/data")]
public async Task<IActionResult> GetModel(BodyParams input)
{
var response = new { Message = "Hello", value = input.valueOne };
return Ok(response);
}
public class BodyParams {
public bool valueOne { get; set; } = true;
}
My goal is that the user can call https://localhost:7222/data with no headers or body needed at all, and will get the response - BodyParams will be used with the default value of true. Currently, from postman, or from the browser, I get a 415 response.
I've worked through several suggestions on stack and git but nothing seems to be working for me. Specifically, I have tried:
Adding [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] into the controller, but this makes no difference unless I provide an empty {} json object in the body. This is not what I want.
Making BodyParams nullable - again, no change.
Adding .AddControllers(opt => opt.AllowEmptyInputInBodyModelBinding = true)... again, no change.
I Implemented the solution suggested here using the attribute modification in the comment by #HappyGoLucky. Again, this did not give the desired outcome, but it did change the response to : 400 - "The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true."
I tried modifying the solution in (4) to manually set context.HttpContext.Request.Body to an empty json object... but I can't figure out the syntax for this because it need to be a byte array and at that point I feel like I am way over complicating this.
How can I get the controller to use BodyParams with default values in the case that the user provides no body and no headers at all?
You can achieve that using a Minimal API.
app.MapGet("/data",
async (HttpRequest httpRequest) =>
{
var value = true;
if (Equals(httpRequest.GetTypedHeaders().ContentType, MediaTypeHeaderValue.Parse("application/json")))
{
var bodyParams = await httpRequest.ReadFromJsonAsync<BodyParams>();
if (bodyParams is not null) value = bodyParams.ValueOne;
}
var response = new {Message = "Hello", value};
return Results.Ok(response);
});
So, as there doesn't seem to be a more straightforward answer, I have currently gone with the approach number 5) from the OP, and just tweaking the code from there very slightly.
All this does is act as an action which checks the if the user has passed in any body json. If not, then it adds in an empty anonymous type. The behaviour then is to use the default True value from the BodyParams class.
The full code for the action class is:
internal class AllowMissingContentTypeForEmptyBodyConvention : Attribute, IActionModelConvention
{
public void Apply(ActionModel action)
{
action.Filters.Add(new AllowMissingContentTypeForEmptyBodyFilter());
}
private class AllowMissingContentTypeForEmptyBodyFilter : IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
if (!context.HttpContext.Request.HasJsonContentType()
&& (context.HttpContext.Request.ContentLength == default
|| context.HttpContext.Request.ContentLength == 0))
{
context.HttpContext.Request.ContentType = "application/json";
var str = new { };
//convert string to jsontype
var json = JsonConvert.SerializeObject(str);
//modified stream
var requestData = Encoding.UTF8.GetBytes(json);
context.HttpContext.Request.Body = new MemoryStream(requestData);
}
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
// Do nothing
}
}
}
Then you can add this to any of your controllers using [AllowMissingContentTypeForEmptyBodyConvention]

Serialize JSON from dynamic ignoring a C# keyword

I'm using Json.net and all I need from it is the simplest form of creating a JSON string to send up as an HTTP POST. For that reason, I don't want to create a new custom object just to be able to serialize it correctly. So I chose the dynamic method in Json.net.
The JSON that I need looks something like this:
{
root: {
header: {
namespace: "",
name: ""
},
body: {
email: email
myArray:[
{
item1: "",
item2: ""
},
{
item3: "",
item4: ""
},
]
}
}
}
So I tried to do the following:
dynamic httpRequestBody = new JObject();
httpRequestBody.root = new JObject();
httpRequestBody.root.header = new JObject();
httpRequestBody.root.header.namespace = "";
httpRequestBody.root.header.name = "name;
But since "namespace" is a C# keyword, it doesn't let me use it. Is there a way to get around it since I need "namespace" as part of the JSON? If not, what's the simplest way of creating this JSON string?
To clarify, I don't have a very strong opinion against creating my own little class and serialize it, but it feels like since all i need is to send some jSON up and forget about it, I should be able to create it on the fly.
Rather than upcasting your JObject to be dynamic, keep it as an explicitly typed variable. JObject implements IDictionary<string, JToken> which can be used, along with the implicit conversion operators from primitive types to JToken, to set a property value of any name to any primitive value or other JToken:
var header = new JObject();
header["namespace"] = ""; // Here we use the implicit operator from string to JToken
header["name"] = "name";
httpRequestBody["root"]["header"] = header;
Using explicit typing also allows for compile-time checking of code correctness, and may improve performance.
Demo fiddle here.

How to use built-in xml or json formatter for custom accept header value in .Net Core 2.0

Update:
I have uploaded a small test project to github: link
I am creating a small web service with .Net Core 2, and would like to give the ability to clients to specify if they need navigational info in the response or not. The web api should only support xml and json, but it would be nice if clients could use
Accept: application/xml+hateoas
or
Accept: application/json+hateoas
in their request.
I tried setting up my AddMvc method like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.RespectBrowserAcceptHeader = true;
options.ReturnHttpNotAcceptable = true;
options.FormatterMappings.SetMediaTypeMappingForFormat(
"xml", MediaTypeHeaderValue.Parse("application/xml"));
options.FormatterMappings.SetMediaTypeMappingForFormat(
"json", MediaTypeHeaderValue.Parse("application/json"));
options.FormatterMappings.SetMediaTypeMappingForFormat(
"xml+hateoas", MediaTypeHeaderValue.Parse("application/xml"));
options.FormatterMappings.SetMediaTypeMappingForFormat(
"json+hateoas", MediaTypeHeaderValue.Parse("application/json"));
})
.AddJsonOptions(options => {
// Force Camel Case to JSON
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
})
.AddXmlSerializerFormatters()
.AddXmlDataContractSerializerFormatters()
;
And I am using the accept header in my controller methods to differentiate between normal xml/json response, and hateoas-like response, like this:
[HttpGet]
[Route("GetAllSomething")]
public async Task<IActionResult> GetAllSomething([FromHeader(Name = "Accept")]string accept)
{
...
bool generateLinks = !string.IsNullOrWhiteSpace(accept) && accept.ToLower().EndsWith("hateoas");
...
if (generateLinks)
{
AddNavigationLink(Url.Link("GetSomethingById", new { Something.Id }), "self", "GET");
}
...
}
So, in short, I do not want to create custom formatters, because the only "custom" thing is to either include or exclude navigational links in my response, but the response itself should be xml or json based on the Accept header value.
My model class looks like this (with mainly strings and basic values in it):
[DataContract]
public class SomethingResponse
{
[DataMember]
public int Id { get; private set; }
When calling my service from Fiddler, I got the following results for the different Accept values:
Accept: application/json -> Status code 200 with only the requested data.
Accept: application/json+hateoas -> Status code 406 (Not Acceptable).
Accept: application/xml -> Status code 504. [Fiddler] ReadResponse() failed: The server did not return a complete response for this request. Server returned 468 bytes.
Accept: application/xml+hateoas -> Status code 406 (Not Acceptable).
Could someone tell me which setting is wrong?
Mapping of format to Media Type (SetMediaTypeMappingForFormat calls) works not as you expect. This mapping does not use Accept header in the request. It reads requested format from parameter named format in route data or URL query string. You should also mark your controller or action with FormatFilter attribute. There are several good articles about response formatting based on FormatFilter attribute, check here and here.
To fix your current format mappings, you should do the following:
Rename format so that it does not contain plus sign. Special + character will give you troubles when passed in URL. It's better to replace it with -:
options.FormatterMappings.SetMediaTypeMappingForFormat(
"xml-hateoas", MediaTypeHeaderValue.Parse("application/xml"));
options.FormatterMappings.SetMediaTypeMappingForFormat(
"json-hateoas", MediaTypeHeaderValue.Parse("application/json"));
Add format parameter to the route:
[Route("GetAllSomething/{format}")]
Format used for format mapping can't be extracted from Accept header, so you will pass it in the URL. Since you need to know the format for the logic in your controller, you could map above format from the route to action parameter to avoid duplication in Accept header:
public async Task<IActionResult> GetAllSomething(string format)
Now you don't need to pass required format in Accept header because the format will be mapped from request URL.
Mark controller or action with FormatFilter attribute.
The final action:
[HttpGet]
[Route("GetAllSomething/{format}")]
[FormatFilter]
public async Task<IActionResult> GetAllSomething(string format)
{
bool generateLinks = !string.IsNullOrWhiteSpace(format) && format.ToLower().EndsWith("hateoas");
// ...
return await Task.FromResult(Ok(new SomeModel { SomeProperty = "Test" }));
}
Now if you request URL /GetAllSomething/xml-hateoas (even with missing Accept header), FormatFilter will map format value of xml-hateoas to application/xml and XML formatter will be used for the response. Requested format will also be accessible in format parameter of GetAllSomething action.
Sample Project with formatter mappings on GitHub
Besides formatter mappings, you could achieve your goal by adding new supported media types to existing Media Types Formatters. Supported media types are stored in OutputFormatter.SupportedMediaTypes collection and are filled in constructor of concrete output formatter, e.g. XmlSerializerOutputFormatter. You could create the formatter instance by yourself (instead of using AddXmlSerializerFormatters extension call) and add required media types to SupportedMediaTypes collection. To adjust JSON formatter, which is added by default, just find its instance in options.OutputFormatters:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.RespectBrowserAcceptHeader = true;
options.ReturnHttpNotAcceptable = true;
options.InputFormatters.Add(new XmlSerializerInputFormatter());
var xmlOutputFormatter = new XmlSerializerOutputFormatter();
xmlOutputFormatter.SupportedMediaTypes.Add("application/xml+hateoas");
options.OutputFormatters.Add(xmlOutputFormatter);
var jsonOutputFormatter = options.OutputFormatters.OfType<JsonOutputFormatter>().FirstOrDefault();
jsonOutputFormatter?.SupportedMediaTypes.Add("application/json+hateoas");
})
.AddJsonOptions(options => {
// Force Camel Case to JSON
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
})
.AddXmlDataContractSerializerFormatters();
}
In this case GetAllSomething should be the same as in your original question. You should also pass required format in Accept header, e.g. Accept: application/xml+hateoas.
Sample Project with custom media types on GitHub

A simple POST request to Web API not hitting the API at all

From my MVC application, I am trying to make a POST request to these sample end-points (actions) in an API controller named MembershipController:
[HttpPost]
public string GetFoo([FromBody]string foo)
{
return string.Concat("This is foo: ", foo);
}
[HttpPost]
public string GetBar([FromBody]int bar)
{
return string.Concat("This is bar: ", bar.ToString());
}
[HttpPost]
public IUser CreateNew([FromBody]NewUserAccountInfo newUserAccountInfo)
{
return new User();
}
Here's the client code:
var num = new WebAPIClient().PostAsXmlAsync<int, string>("api/membership/GetBar", 4).Result;
And here's the code for my WebAPIClient class:
public class WebAPIClient
{
private string _baseUri = null;
public WebAPIClient()
{
// TO DO: Make this configurable
_baseUri = "http://localhost:54488/";
}
public async Task<R> PostAsXmlAsync<T, R>(string uri, T value)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(_baseUri);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
var requestUri = new Uri(client.BaseAddress, uri);
var response = await client.PostAsXmlAsync<T>(requestUri, value);
response.EnsureSuccessStatusCode();
var taskOfR = await response.Content.ReadAsAsync<R>();
return taskOfR;
}
}
}
I have the following default route defined for the Web API:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
UPDATE
My code breaks into the debugger until the time the PostAsXmlAsync method on the System.Net.HttpClient code is called. However, no request shows up in Fiddler.
However, if I try to compose a POST request in Fiddler or try to fire a GET request via the browser to one of the API end-points, the POST request composed via Fiddler tells me that I am not sending any data and that I must. The browser sent GET request rightly tells me that the action does not support a GET request.
It just seems like the System.Net.HttpClient class is not sending the POST request properly.
One of the most usual problems is that you don't use the appropriate attribute.
Take into account that there are attributes for ASP.NET MVC and ASP.NET Web API with the same name, but which live in different namespaces:
For Web API you must use the one in System.Web.Http
For MVC, the one in System.Web.MVc
This is a very very usual error, and it affects to allkind of things that exist for both MVC and Web API. So you must be very careful when using something which can exists in bith worlds (for example filters, attributes, or dependency injection registration).
I experienced a similar problem (may not be same one though). In my case, I hadn't given name attribute to the input element. I only figured that out when fiddler showed no post data being sent to the server (just like your case)
<input id="test" name="xyz" type="text" />
Adding the name attribute in the input tag fixed my problem.
However, there is one more thing to note. WebAPI does not put form data into parameters directly. Either you have to create an object with those properties and put that object in the parameter of the post controller. Or you could put no parameters at all like this:
[Route("name/add")]
public async Task Post()
{
if (!Request.Content.IsMimeMultipartContent())
{
return;
}
var provider = PostHelper.GetMultipartProvider();
var result = await Request.Content.ReadAsMultipartAsync(provider);
var clientId = result.FormData["xyz"];
...
Try changing the FromBody to FromUri.
If the parameter is a "simple" type, Web API tries to get the value from the URI. Simple types include the .NET primitive types (int, bool, double, and so forth), plus TimeSpan, DateTime, Guid, decimal, and string, plus any type with a type converter that can convert from a string.
For complex types, Web API tries to read the value from the message body, using a media-type formatter.
Remove FromBody at all and don't make any restrictions in passing parameters (it can be passed at this time either in uri, query string or form submissions (which is kinda a similar to query strings)
[HttpPost]
public string GetFoo(string foo){...}
It will be implicitly parsed and passed.

JSON conversion issue from JS to Google Cloud Endpoints server

When executing the call I get a server exception:
var x = '{"name": "Test","address": { "street": "White House"}}';
gapi.client.realestate.create(x).execute(function(resp) {
console.log(resp);
});
java.lang.ClassCastException: com.google.appengine.repackaged.org.codehaus.jackson.node.TextNode cannot be cast to com.google.appengine.repackaged.org.codehaus.jackson.node.ObjectNode
at com.google.api.server.spi.tools.devserver.RpcApiServlet.doSingleBackendCall(RpcApiServlet.java:131)
at com.google.api.server.spi.tools.devserver.RpcApiServlet.service(RpcApiServlet.java:112)
If I use the same JSON expression in API Explorer, it works. The #ApiMethod is defined as:
#ApiMethod(name = "create", path = "properties", httpMethod = HttpMethod.POST)
public void create(RealEstateProperty property, User user) throws Exception {
The JS Client library methods expect JSON objects and not JSON strings, it does the encoding for the call itself. Also since this is a POST request you will have to define the POST body as a resource parameter for the method (which isn't really well documented unfortunately)
This should work:
var x = {"name": "Test","address": { "street": "White House"}};
gapi.client.realestate.create({"resource": x}).execute( ...

Resources