JamesNK/Newtonsoft.Json · error · JsonException
Unresolved circular reference for type '{0}'. Explicitly def
Error message
Unresolved circular reference for type '{0}'. Explicitly define an Id for the type using a JsonObject/JsonArray attribute or automatically generate a type Id using the UndefinedSchemaIdHandling property. What it means
Thrown by JsonSchemaGenerator.GenerateInternal when it detects that the type being processed is already on the generation stack (a cycle), and neither a JsonObject/JsonArray Id attribute nor UndefinedSchemaIdHandling (UseTypeName/UseAssemblyQualifiedName) is configured to break the cycle. Without an id, the generator cannot emit a $ref and would recurse infinitely, so it aborts.
Source
Thrown at Src/Newtonsoft.Json/Schema/JsonSchemaGenerator.cs:282
// resolved schema is not null but referencing member allows nulls
// change resolved schema to allow nulls. hacky but what are ya gonna do?
if (valueRequired != Required.Always && !HasFlag(resolvedSchema.Type, JsonSchemaType.Null))
{
resolvedSchema.Type |= JsonSchemaType.Null;
}
if (required && resolvedSchema.Required != true)
{
resolvedSchema.Required = true;
}
return resolvedSchema;
}
}
// test for unresolved circular reference
if (_stack.Any(tc => tc.Type == type))
{
throw new JsonException("Unresolved circular reference for type '{0}'. Explicitly define an Id for the type using a JsonObject/JsonArray attribute or automatically generate a type Id using the UndefinedSchemaIdHandling property.".FormatWith(CultureInfo.InvariantCulture, type));
}
JsonContract contract = ContractResolver.ResolveContract(type);
JsonConverter converter = contract.Converter ?? contract.InternalConverter;
Push(new TypeSchema(type, new JsonSchema()));
if (explicitId != null)
{
CurrentSchema.Id = explicitId;
}
if (required)
{
CurrentSchema.Required = true;
}
CurrentSchema.Title = GetTitle(type);
CurrentSchema.Description = GetDescription(type);View on GitHub (pinned to 4f73e74372)
Solutions
- Decorate the cyclic type with [JsonObject(Id = "node")] or [JsonArray(Id = "node")] so the generator emits a $ref.
- Set generator.UndefinedSchemaIdHandling = UndefinedSchemaIdHandling.UseTypeName to auto-generate ids from type names.
- Break the cycle by marking the back-reference property with [JsonIgnore].
Example fix
// before
class Node { public Node Next { get; set; } }
gen.Generate(typeof(Node)); // throws
// after
[JsonObject(Id = "node")]
class Node { public Node Next { get; set; } }
gen.Generate(typeof(Node)); Defensive patterns
Strategy: validation
Validate before calling
static bool HasSchemaId(Type type)
{
var attr = type.GetCustomAttributes(typeof(JsonObjectAttribute), false)
.Cast<JsonObjectAttribute>().FirstOrDefault();
return attr != null && !string.IsNullOrEmpty(attr.Id);
} Try / catch
try { generator.Generate(typeof(T)); }
catch (JsonException ex) when (ex.Message.StartsWith("Unresolved circular reference"))
{ /* add [JsonObject(Id=...)] or set UndefinedSchemaIdHandling */ } Prevention
- Decorate recursive types with [JsonObject(Id = "...")] or [JsonArray(Id = "...")].
- Set UndefinedSchemaIdHandling.UseTypeName for automatic ids.
- Mark back-references with [JsonIgnore] to break cycles.
When it happens
Trigger: Calling JsonSchemaGenerator.Generate on a type with a self-referencing or mutually-referencing property and no schema id configured. Example: class Node { public Node Next { get; set; } } with the default UndefinedSchemaIdHandling (None) throws; class A { public B B { get; set; } } / class B { public A A { get; set; } } likewise.
Common situations: Domain models with parent/child or graph relationships (tree nodes, linked lists, graphs) serialized to JSON Schema; upgrading from a version where ids were inferred; changing UndefinedSchemaIdHandling back to None.
Related errors
- Unexpected contract type: {0}
- Could not resolve schema reference '{0}'.
- Property {0} has already been defined in schema.
- Invalid JSON schema type: {0}
- Newtonsoft.Json serialization is not compatible with trimmin
AI-assisted analysis of JamesNK/Newtonsoft.Json@4f73e74372 (2026-08-07).
Data as JSON: /api/errors/4dca1226e12c8d33.
Report an issue: GitHub.