-2

I have set up a new API endpoint (shown below) that expects a JObject in the request body. I am working towards a more complex example, but currently am attempting this with a simple boolean object.

However, when I attempt to hit the endpoint via Swagger, I get a http 400 error

The JSON value could not be converted to Newtonsoft.Json.Linq.JToken. Path: $.ShouldTakeAction | LineNumber: 1 | BytePositionInLine: 26

I've tried a few variations of the request body with no luck. What am I doing wrong?

[HttpPost]
[Route("CreateNodeWithAction")]
public ActionResult<string> CreateNodeWithAction([FromBody] JObject request)
{
 if (request == null)
 {
 return BadRequest("Request body is null.");
 }
 bool shouldTakeAction = request.Value<bool>("ShouldTakeAction");
 if (shouldTakeAction)
 {
 return Ok("Action taken.");
 }
 else
 {
 return Ok("No action taken.");
 }
}

Swagger Parameters

Swagger Curl

Sample of existing endpoint that I want to hit (the other was what I 'thought' I needed to create)

[HttpPost] 
[Route("[action]")] 
public IActionResult CreateMyObject(string field1, string field2, bool overrideSomething) 
{ // Do Stuff } 
asked Aug 7, 2025 at 12:29
5
  • 4
    This isn't simple at all. That JObject contains on information. Might as well use object. APIs just don't work that way. It's ASP.NET's job to serialize the request or response objects to JSON, not the application's. Try with proper classes instead of JObject. Besides, ASP.NET Core uses System.Text.Json, not JSON.NET Commented Aug 7, 2025 at 12:31
  • 1
    OpenAPI/Swagger is used to specify what the request and response objects are. In ASP.NET Core the OpenAPI document is generated automatically from the request/response objects. The screenshot you posted shows Swagger UI, just one way of visualizing the OpenAPI document. Check ASP.NET Core's OpenAPI overview and Use Swagger UI for testing Commented Aug 7, 2025 at 12:38
  • 1
    BTW the error isn't returned by Swagger UI but ASP.NET itself. It says it can't convert the JSON string to a JObject instance. If you use ASP.NET Core, the runtime has no idea what JObject even is. It will try to deserialize attributes into object properties, and JObject doesn't have a ShouldTakeAction property Commented Aug 7, 2025 at 12:45
  • 3
    Why are you trying to use JObject instead of a simple custom class which defines the structure that the API expects? If there's a driving reason behind this, there's likely a better way to achieve that goal. Alternatively, if the reason was an attempt at simplicity, that attempt clearly introduced complexity instead. Commented Aug 7, 2025 at 13:09
  • David-The reason I am not using a custom object is we have a large number of APIs that simply take a series of parameters (2 string and a boolean, or an int and string). I don't want to generate a custom class object for each API endpoint just to pass those parameters. So instead I'd like to simply pass a 'collection' that contains those items. Commented Aug 7, 2025 at 17:57

2 Answers 2

2

The error is returned by ASP.NET Core itself. It can't parse the JSON text into a JObject. It doesn't even know what a JObject is. ASP.NET Core uses System.Text.Json, not JSON.NET. When it sees JObject it assumes that's your actual request DTO object and will try to deserialize the "ShouldTakeAction" attribute into a JObject.ShouldTakeAction property which doesn't exist.

ASP.NET Core also validates the incoming request, ensuring it matches the properties, types and validation defined on the request DTO. If the validation fails you get back a standardized 400 error response with a Problem Details body. That's what your screenshot shows.

APIs almost never accept open-ended requests. OpenAPI (whose old name was Swagger) is a way to specify what the API accepts and returns, not the UI you use for testing. ASP.NET Core can automatically generate the Open API schema document just from the request and response objects. The ASP.NET Core Web API project template already generates an Open API schema. You'll find the details in the docs, in the OpenAPI Overview

The simplest way to create an API that accepts the request you posted and returns a string is this:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseHttpsRedirection();
app.MapPost("/CreateNodeWithAction", (SomeAction it) =>
{
 return it.ShouldTakeAction
 ? Results.Ok("Action Taken")
 : Results.Ok("No Action Taken");
});
app.Run();
record SomeAction(bool ShouldTakeAction);

You can post to this using eg curl and get a response back.

curl -X 'POST' \
 'http://localhost:5291/CreateNodeWithAction' \
 -H 'accept: */*' \
 -H 'Content-Type: application/json' \
 -d '{
 "shouldTakeAction": true
}'

The default Web API project template adds OpenAPI out of the box. All that's needed are the AddOpenAPI call that installs the OpenAPI middleware and MapOpenAPI which exposes the document at openapi/v1.json

var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
 app.MapOpenApi();
}
app.UseHttpsRedirection();
app.MapPost("/CreateNodeWithAction", (SomeAction it) =>
{
 return it.ShouldTakeAction
 ? Results.Ok("Action Taken")
 : Results.Ok("No Action Taken");
});
app.Run();

This is the generated OpenAPI document :

{
 "openapi": "3.1.1",
 "info": {
 "title": "actiontest | v1",
 "version": "1.0.0"
 },
 "servers": [
 {
 "url": "http://localhost:5291/"
 }
 ],
 "paths": {
 "/CreateNodeWithAction": {
 "post": {
 "tags": [
 "actiontest"
 ],
 "requestBody": {
 "content": {
 "application/json": {
 "schema": {
 "$ref": "#/components/schemas/SomeAction"
 }
 }
 },
 "required": true
 },
 "responses": {
 "200": {
 "description": "OK"
 }
 }
 }
 }
 },
 "components": {
 "schemas": {
 "SomeAction": {
 "required": [
 "shouldTakeAction"
 ],
 "type": "object",
 "properties": {
 "shouldTakeAction": {
 "type": "boolean"
 }
 }
 }
 }
 },
 "tags": [
 {
 "name": "actiontest"
 }
 ]
}

The Use OpenAPI documents page in the docs shows how you can use various packages to display the Open API document or call the API from the UI. Swagger UI, which is shown in the question's screenshots, is just one option

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
 app.MapOpenApi();
 app.UseSwaggerUI(options =>
 {
 options.SwaggerEndpoint("/openapi/v1.json", "v1");
 });
}

If you really want to accept "everything" you can use JsonObject instead of a DTO. In that case though the OpenAPI schema becomes useless (since everything is acceptable) and you'll have to manually handle everything - lookups, parsing, casting, validations. What if the request contains ShouldTakeAction or shouldtakeaction ? What if it's missing? What if it contains a string instead of a boolean?

And how will you tell clients what they should send?

//Really bad idea, don't use
app.MapPost("/CreateNodeWithAction", (JsonObject it) =>
{
 return it["ShouldTakeAction"].GetValue<bool>()
 ? Results.Ok("Action Taken")
 : Results.Ok("No Action Taken");
});
answered Aug 7, 2025 at 13:34
Sign up to request clarification or add additional context in comments.

5 Comments

There is a lot of information here that I hope to read through this evening. While I read through that let me expand on my post. We have a few dozen APIs that simply take a variety of parameters (different mix of strings and bools, etc). I am trying to leverage these APIs for our test framework to do some of the heavy lifting. I want to be able to pass them object's via "Request Body Parameter" rather than "Query String" as some of the APIs take complex objects (thing custom data object + strings).
I truly appreciate everyone's response. I now got a working test: API endpoint has a class as a parameter ([FromBody] MyParamClass request), and I can hit this with an HttpClient that has a HttpContent object that's a serialized (JsonConvert) of "new { field1 = "value", field2 = "value"}. My (updated) question: can I reuse my existing endpoint w/out having to make a newone using a class object as a parameter? Or at least make a new endpoint that takes some sort of dynamic object so I don't have to create a class for each parameter set every api endpoint uses.
If you have a new question it's better to post a new question rather than trying to edit your current one or trying to ask it in the comments. Please don't forget to accept the answer if it's solved your current issue.
I apologize. I wasn't intentionally trying to generate a new question, but rather broaden my understanding as a result of this discussion. Ultimately I am still trying to identify the best way to submit a Request Body to an API Endpoint without significant changes to the existing application.
I showed how to use a JsonObject but it's a bad idea. It's better to post a new question but people will ask why you want that and tell you that 1) that's a bad idea, you'll end up with a ton of code in the "dynamic" class to handle the various business cases and all the features you lose and 2) you already have a lot of dynamic and reusable features available through the Controller base class and middleware. So why do you need a "dynamic" component? There's almost certainly a better way, eg through custom middleware, or an injected service or something else
0

Based on information I got here and some trial, I currently have the below implementation (though this actually requires I create 'RequestBody' instances of any API I wish to use).

public class TestClass()
{
 public void MyTest()
 {
 var data = new { projectName = "MyProject", nodeTypeName = "Basic" };
 string uri = "http://localhost/Api/v1/CreateNode_BodyRequest";
 
 var response = myApi.PostDataAsync(uri, data);
 }
 
 public async Task<string> PostDataAsync<T>(string uri, T data)
 {
 try
 {
 // Prepare the data to be sent in the request body
 string jsonContent = Newtonsoft.Json.JsonConvert.SerializeObject(data);
 HttpContent content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
 
 // Send POST request
 HttpResponseMessage response = await _client.PostAsync(uri, content);
 
 response.EnsureSuccessStatusCode(); // Throws exception for non-success status codes.
 
 string responseBody = await response.Content.ReadAsStringAsync();
 return responseBody;
 }
 catch (HttpRequestException ex)
 {
 Console.WriteLine($"Error posting data: {ex.Message}");
 throw; // Re-throw the exception for handling in the calling code
 }
 }
}
public class ApiController()
{
 // This results in a new API endpoint on our swagger page (not desired)
 [HttpPost]
 [Route("[action]_FromBody")]
 public ActionResult<Guid> CreateNode([FromBody] MyExtension.CreateNodeRequest request)
 {
 return CreateNode(request.ProjectName, request.NodeType)
 }
 [HttpPost]
 [Route("[action]")]
 public ActioniResult<Guid> CreateNode(string projectName, string nodeTypeName)
 {
 // Do logic
 // return Guid
 }
}
public class MyExtension()
{
 // Unique per API Endpoint, so I'll have to create a new one for each 'parameter' list
 public class CreateNodeRequest()
 {
 string ProjectName { get; set; }
 string NodeType { get; set; }
 }
}
answered Aug 8, 2025 at 16:01

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.