Skip to main content

Fixed: A Missing Number in a JSON Request Becomes 0 in an ASP.NET Core Minimal API

· 5 min read
Jagdish Kumawat
Founder @ Dewiride

A request with no price was accepted, and the product's price quietly became 0. No error, no warning. A C# decimal can't be null, so when the JSON leaves it out, it simply keeps its default value, and [Required] has nothing to complain about. Making the property decimal? fixes that.

What I Was Doing​

In a .NET 10 minimal API with AddValidation, a request record had plain number properties:

An endpoint with a plain record
public sealed record PlainProductRequest(string Sku, decimal UnitPrice, int ReorderLevel);

app.MapPost("/demo/plain-record", (PlainProductRequest request) => Results.Ok(request));

I sent a request that didn't include the numbers:

Request
POST http://localhost:5190/demo/plain-record
Content-Type: application/json

{
"sku": "CBL-NOPRICE"
}

What I Expected​

A 400 saying the price is missing. A product without a price makes no sense.

What I Got​

Response
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
"sku": "CBL-NOPRICE",
"unitPrice": 0,
"reorderLevel": 0
}

200 OK, with "unitPrice": 0.

A terminal with a POST that leaves out unitPrice and reorderLevel, answered with 200 OK, where the body shows unitPrice 0 and reorderLevel 0.

What I Checked​

What a decimal's default is. 0. C#'s value types always have a value, and a record's constructor parameter gets its type's default when System.Text.Json has nothing to give it.

Whether [Required] helps. Not on a plain value type. RequiredAttribute checks for null, and a decimal is never null, so 0 passes. There's no way for validation to tell "the client sent 0" from "the client sent nothing", because by then both look the same.

What a nullable type does. I made the same record with decimal?:

An endpoint with a nullable price
public sealed record NullableProductRequest(
[Required] string Sku,
[Required] decimal? UnitPrice);
Response (the same request)
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json

{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"UnitPrice": [
"The UnitPrice field is required."
]
},
"traceId": "00-f2896a55b18f9370a54b0ccda2567abb-61ac781636f54bb5-00"
}

Why It Happens​

JSON has three cases for a number: a value, null, and missing. A plain decimal can only represent the first, so the other two collapse into 0. decimal? has room for "no value", and [Required] can see that.

The Fix​

Make every number the client must send nullable, and mark it [Required]:

src/Dewiride.Inventory.Api/Features/Products/ProductContracts.cs (excerpt)
public sealed record CreateProductRequest(
[Required, StringLength(32)] string Sku,
[Required, StringLength(200)] string Name,
string? Description,
[Range(1, int.MaxValue)] int CategoryId,
[Required, Money] decimal? UnitPrice,
[Range(0, int.MaxValue)] int ReorderLevel);

Notice the other two numbers here. CategoryId stays an int, because [Range(1, …)] refuses its default of 0 anyway. ReorderLevel stays an int, because 0 is a fine default for it. Only UnitPrice needed the change, and the endpoint reads it with GetValueOrDefault(), since validation has ruled out null.

After the Fix​

A missing price:

Response
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json

{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"UnitPrice": [
"The UnitPrice field is required."
]
},
"traceId": "00-3ef294d4f4809b6060ef90706edb6747-0c75ca43a6d85633-00"
}

A real price of zero is still allowed, which is the difference this makes:

Request
POST http://localhost:5190/demo/nullable-record
Content-Type: application/json

{
"sku": "CBL-PRICE",
"unitPrice": 0
}
Response
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
"sku": "CBL-PRICE",
"unitPrice": 0
}

A terminal with a POST with no unitPrice answered with 400 Bad Request and the message The UnitPrice field is required, and a POST with unitPrice 0 answered with 200 OK.

Common Mistakes​

MistakeWhat happensDo this instead
[Required] on a plain int or decimalIt does nothingMake it nullable
[Range(0, …)] on a plain decimal0 passes, so a missing value does tooAdd [Required] to a nullable
Treating 0 as "missing" in codeA real zero gets refusedUse a nullable
Using [JsonRequired]It works, but gives clients a bare 400 with no field nameUse [Required] on a nullable

Troubleshooting​

SymptomLikely causeFix
A missing number is stored as 0A plain value type in the requestMake it nullable, with [Required]
A missing bool is falseThe samebool? with [Required]
A missing date is 0001-01-01The sameDateTime? with [Required]
CS8629 after making it nullable.Value without a checkSee that post

FAQ​

Doesn't a non-nullable string have the same problem?​

Differently: a missing string arrives as null, whatever the type says, and [Required] catches that. Value types are the ones that hide it.

Does the entity need to be nullable too?​

No. Keep decimal on the entity and NOT NULL in the column. Only the request has to distinguish missing from zero.

Conclusion​

A number missing from a JSON request becomes 0, because a plain decimal or int always has a value, and [Required] can't see the difference. Use decimal? with [Required] for anything the client must send, and keep the plain type where the default makes sense.

Additional Resources​

Stay Updated

Subscribe to our newsletter for the latest tutorials, tech insights, and developer news.

By subscribing, you agree to our privacy policy. Unsubscribe at any time.