System.Text.Json.JsonException
A JsonException is thrown by System.Text.Json when it cannot read or write JSON. The message has the reason and the path to the value, such as The JSON value could not be converted to System.Int32. Path: $.Id, and the properties LineNumber and BytePositionInLine give the position in the text.
The serializer is strict by default, so data that older serializers accepted, such as numbers in quotes, may fail. Most causes come down to the JSON not matching the type, or the type not being possible to write.
Minimum version: >= Core 3.0
Statistics
Common causes
The path in the message tells you which value failed. These are the usual reasons.
A value of the wrong type
The JSON has a string where the property is a number, or the other way around. Values that are null for a property that cannot be null also end up here.
var customer = JsonSerializer.Deserialize<Customer>("{\"Id\":\"12\"}"); // JsonException: Path: $.Id
Fix the data if you control it. If the other side sends numbers as strings, allow it in the options:
var options = new JsonSerializerOptions { NumberHandling = JsonNumberHandling.AllowReadingFromString };
var customer = JsonSerializer.Deserialize<Customer>("{\"Id\":\"12\"}", options);
JSON that is not valid
A response that is cut off, a body that is empty, or HTML from an error page that is read as JSON. The message says that a character is an invalid start of a value, and the first characters of the text are the best clue.
var customer = JsonSerializer.Deserialize<Customer>("<html>Bad gateway</html>"); // JsonException
Check the status code and the content type before you read the body, and catch the exception where you parse data from outside:
var body = "<html>Bad gateway</html>";
try
{
var customer = JsonSerializer.Deserialize<Customer>(body);
}
catch (JsonException e)
{
logger.LogWarning(e, "Invalid JSON at {Path}", e.Path);
}
Objects that point back to themselves
A parent that has a list of children, and children that point to the parent, make a loop. The serializer follows it until it reaches the maximum depth, and throws A possible object cycle was detected.
public class Category
{
public string Name { get; set; }
public Category Parent { get; set; }
public List<Category> Children { get; set; } = new();
}
Leave the back reference out of the JSON with [JsonIgnore]. ReferenceHandler.IgnoreCycles in the options does the same for all types:
public class Category
{
public string Name { get; set; }
[JsonIgnore]
public Category Parent { get; set; }
public List<Category> Children { get; set; } = new();
}
A required property that is missing
A property with the required modifier or [JsonRequired] must be in the JSON. If it is not, the serializer throws, and lists the names that were missing.
public class Product
{
public required string Sku { get; set; }
}
Make the sender include it, or give the property a default value if it is optional:
public class Product
{
public string Sku { get; set; } = "";
}
How to fix it and prevent it
Read the path in the message
The path, such as $.Orders[3].Price, points to the value that failed. Log it together with the part of the JSON around it, but not data that must stay private.
Set the options once
Use one JsonSerializerOptions for the application and set the rules you need there: property name casing, number handling, enums as strings and the handling of cycles. A single instance is also faster, because the type information is cached.
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
NumberHandling = JsonNumberHandling.AllowReadingFromString,
ReferenceHandler = ReferenceHandler.IgnoreCycles
};
options.Converters.Add(new JsonStringEnumConverter());
Check responses before you parse them
Check IsSuccessStatusCode and the content type of a response before you deserialize it. A proxy or an API that returns an error page turns a clear problem into a parse error.
How to read the stack trace
The message has the path, and the inner exception, when there is one, has the details of the failing token:
System.Text.Json.JsonException: The JSON value could not be converted to System.Int32. Path: $.Id | LineNumber: 0 | BytePositionInLine: 11.
---> System.InvalidOperationException: Cannot get the value of a token type 'String' as a number.
at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_ExpectedNumber(JsonTokenType tokenType)
at System.Text.Json.Utf8JsonReader.TryGetInt32(Int32& value)
at System.Text.Json.Utf8JsonReader.GetInt32()
at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1.ReadJsonAndSetMember(Object obj, ReadStack& state, Utf8JsonReader& reader)
at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value, Boolean& isPopulatedValue)
at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, T& value, JsonSerializerOptions options, ReadStack& state)
--- End of inner exception stack trace ---
at System.Text.Json.ThrowHelper.ReThrowWithPath(ReadStack& state, Utf8JsonReader& reader, Exception ex)
at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, T& value, JsonSerializerOptions options, ReadStack& state)
at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.Deserialize(Utf8JsonReader& reader, ReadStack& state)
at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 utf8Json, JsonTypeInfo`1 jsonTypeInfo, Nullable`1 actualByteCount)
at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 json, JsonTypeInfo`1 jsonTypeInfo)
at System.Text.Json.JsonSerializer.Deserialize[TValue](String json, JsonSerializerOptions options)
at Shop.Services.PriceFeed.Parse(String json) in C:\src\Shop\Services\PriceFeed.cs:line 11
at Program.<Main>$(String[] args) in C:\src\Shop\Program.cs:line 4
The path is $.Id, and the inner exception says that a string token cannot be read as a number. The call that deserialized the text is on line 11 of PriceFeed.cs, so look at the JSON that was passed to it, not at the code in System.Text.Json.
Should you catch it?
Yes, where you read JSON from the outside: a request body, a file or a response from another service. Catch JsonException, log the path, and return an error to the caller.
For JSON that you write yourself, do not catch it. An exception there is a bug in the code, such as a cycle.
Find JsonException before your users do
elmah.io logs every unhandled exception in your .NET application with its stack trace and request details, groups identical errors and notifies you when a new one appears.
Start free trialRelated exceptions
- FormatException is thrown when a string is not in the right format for a value.
- InvalidCastException is thrown when an object cannot be converted to a type.
- NotSupportedException is thrown when the serializer does not support a type.
Frequently asked questions
How do I find the value that failed?
Read the path in the message, or e.Path, and the position in LineNumber and BytePositionInLine. Together they point at the value.
Why does it work with Newtonsoft.Json?
Newtonsoft is more forgiving. It reads numbers in quotes, ignores the casing of names and handles some cycles in other ways. System.Text.Json is strict by default, so set the options you need.
How do I ignore unknown or missing properties?
Unknown properties are ignored by default. For missing ones, give the property a default value and do not use required.
How do I avoid the object cycle error?
Add [JsonIgnore] to the back reference, or set ReferenceHandler.IgnoreCycles. Sending DTOs instead of the entities is a cleaner solution.
Let your AI agent track it down
Connect Claude Code, Cursor, VS Code or Visual Studio to the elmah.io MCP server and ask your agent to look into JsonException for you. For example:
The agent reads the stack trace and request details from elmah.io, finds the code in your repository and proposes a fix. In Claude Code, add the server with one command:
claude mcp add --transport http --client-id claudecode elmahio https://mcp.elmah.io/mcp
The MCP server is included on every plan and is currently in beta. Set up the MCP server.
Further reading
YouTube videos
Answers from Stack Overflow
I have tried your code in a new project and the second way seems to work well after installing the package Microsoft.AspNetCore.Mvc.NewtonsoftJson firstly for 3.0
services.AddControllersWithViews()
.AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
Try with a new project and compare the differences.
.NET Core 3.1 Install the package Microsoft.AspNetCore.Mvc.NewtonsoftJson (from https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.NewtonsoftJson/ )
Startup.cs Add service
services.AddControllers().AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
By anjoe. Read the original answer on Stack Overflow.
Check if you await-ed all async methods.
By Mitulát báti. Read the original answer on Stack Overflow.
Update:
Using .NET 6, there is an option for System.Text.Json to ignore circular references like this:
JsonSerializerOptions options = new()
{
ReferenceHandler = ReferenceHandler.IgnoreCycles,
WriteIndented = true
};
The problem with ReferenceHandler.Preserve is that JSON keys are prefixed with $, and this can cause some issues.
Example System.Text.Json ReferenceHandler.IgnoreCycles:
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace SerializeIgnoreCycles
{
public class Employee
{
public string Name { get; set; }
public Employee Manager { get; set; }
public List<Employee> DirectReports { get; set; }
}
public class Program
{
public static void Main()
{
Employee tyler = new()
{
Name = "Tyler Stein"
};
Employee adrian = new()
{
Name = "Adrian King"
};
tyler.DirectReports = new List<Employee> { adrian };
adrian.Manager = tyler;
JsonSerializerOptions options = new()
{
ReferenceHandler = ReferenceHandler.IgnoreCycles,
WriteIndented = true
};
string tylerJson = JsonSerializer.Serialize(tyler, options);
Console.WriteLine($"Tyler serialized:\n{tylerJson}");
Employee tylerDeserialized =
JsonSerializer.Deserialize<Employee>(tylerJson, options);
Console.WriteLine(
"Tyler is manager of Tyler's first direct report: ");
Console.WriteLine(
tylerDeserialized.DirectReports[0].Manager == tylerDeserialized);
}
}
}
// Produces output like the following example:
//
//Tyler serialized:
//{
// "Name": "Tyler Stein",
// "Manager": null,
// "DirectReports": [
// {
// "Name": "Adrian King",
// "Manager": null,
// "DirectReports": null
// }
// ]
//}
//Tyler is manager of Tyler's first direct report:
//False
Source:
Example with Newtonsoft.Json.ReferenceLoopHandling.Ignore
public class Employee
{
public string Name { get; set; }
public Employee Manager { get; set; }
}
Employee joe = new Employee { Name = "Joe User" };
Employee mike = new Employee { Name = "Mike Manager" };
joe.Manager = mike;
mike.Manager = mike;
string json = JsonConvert.SerializeObject(joe, Formatting.Indented, new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
Console.WriteLine(json);
// {
// "Name": "Joe User",
// "Manager": {
// "Name": "Mike Manager"
// }
// }
Original:
I got this error from the default POST method in Controller created with an API Controller with actions, using ADO.NET Entity Framework.
return CreatedAtAction("GetLearningObjective", new { id = learningObjective.Id }, learningObjective);
System.Text.Json.JsonException: A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32. Consider using ReferenceHandler.Preserve on JsonSerializerOptions to support cycles. at System.Text.Json.ThrowHelper.ThrowJsonException_SerializerCycleDetected(Int32 maxDepth)
When calling HttpGet directly from Postman or a browser it worked without a problem. It was solved by editing Startup.cs - services.AddControllers() like this:
services.AddControllers().AddJsonOptions(options =>
{
options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve;
});
You could also solve it like this:
services.AddControllers(options =>
{
options.OutputFormatters.RemoveType<SystemTextJsonOutputFormatter>();
options.OutputFormatters.Add(new SystemTextJsonOutputFormatter(new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
ReferenceHandler = ReferenceHandler.Preserve,
}));
});
How to preserve references and handle or ignore circular references in System.Text.Json
By Ogglas. Read the original answer on Stack Overflow.
Instead of using NewtonsoftJson, I used System.Text.Json.Serialization.
For .NET Core 3.1
In file Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
..........
.......
services.AddControllers().AddJsonOptions(options =>
{
options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
options.JsonSerializerOptions.WriteIndented = true;
});
}
For .NET 6
In file Program.cs:
builder.Services.AddControllers().AddJsonOptions(options =>
{
options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
options.JsonSerializerOptions.WriteIndented = true;
});
By Abishek G Chhetri. Read the original answer on Stack Overflow.
Source: Stack Overflow