JamesNK/Newtonsoft.Json · error · JsonException
Invalid attribute. Both '{0}' and '{1}' in type '{2}' have '
Error message
Invalid attribute. Both '{0}' and '{1}' in type '{2}' have '{3}'. What it means
Each serialization callback attribute ([OnSerializing], [OnSerialized], [OnDeserializing], [OnDeserialized], [OnError]) may be applied to at most one method per type. IsValidCallback tracks the currently registered callback for an attribute and throws a JsonException (line 1317) if a second method with the same attribute is encountered. An unambiguous single callback is required per lifecycle event.
Source
Thrown at Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs:1317
if (type == typeof(DateOnly) || type == typeof(TimeOnly))
{
return true;
}
#endif
return false;
}
private static bool IsValidCallback(MethodInfo method, ParameterInfo[] parameters, Type attributeType, MethodInfo? currentCallback, ref Type? prevAttributeType)
{
if (!method.IsDefined(attributeType, false))
{
return false;
}
if (currentCallback != null)
{
throw new JsonException("Invalid attribute. Both '{0}' and '{1}' in type '{2}' have '{3}'.".FormatWith(CultureInfo.InvariantCulture, method, currentCallback, GetClrTypeFullName(method.DeclaringType!), attributeType));
}
if (prevAttributeType != null)
{
throw new JsonException("Invalid Callback. Method '{3}' in type '{2}' has both '{0}' and '{1}'.".FormatWith(CultureInfo.InvariantCulture, prevAttributeType, attributeType, GetClrTypeFullName(method.DeclaringType!), method));
}
if (method.IsVirtual)
{
throw new JsonException("Virtual Method '{0}' of type '{1}' cannot be marked with '{2}' attribute.".FormatWith(CultureInfo.InvariantCulture, method, GetClrTypeFullName(method.DeclaringType!), attributeType));
}
if (method.ReturnType != typeof(void))
{
throw new JsonException("Serialization Callback '{1}' in type '{0}' must return void.".FormatWith(CultureInfo.InvariantCulture, GetClrTypeFullName(method.DeclaringType!), method));
}
if (attributeType == typeof(OnErrorAttribute))View on GitHub (pinned to 4f73e74372)
Solutions
- Consolidate the two methods into a single method carrying the attribute.
- Remove the attribute from one of the methods.
Example fix
// before
public class Model {
[OnDeserialized] internal void OnDeserializedA(StreamingContext c) {}
[OnDeserialized] internal void OnDeserializedB(StreamingContext c) {} // throws
}
// after
public class Model {
[OnDeserialized] internal void OnDeserialized(StreamingContext c) {}
} Defensive patterns
Strategy: validation
Validate before calling
var attrs = new[] { typeof(OnSerializingAttribute), typeof(OnSerializedAttribute), typeof(OnDeserializingAttribute), typeof(OnDeserializedAttribute), typeof(OnErrorAttribute) };
foreach (var a in attrs)
{
var methods = typeof(T).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where(m => m.IsDefined(a, false)).ToList();
if (methods.Count > 1)
throw new InvalidOperationException($"{methods.Count} methods on {typeof(T)} carry {a.Name}; only one allowed.");
} Type guard
static bool HasAtMostOneCallbackPerAttribute(Type t)
{
var attrs = new[] { typeof(OnSerializingAttribute), typeof(OnSerializedAttribute), typeof(OnDeserializingAttribute), typeof(OnDeserializedAttribute), typeof(OnErrorAttribute) };
return attrs.All(a => t.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Count(m => m.IsDefined(a, false)) <= 1);
} Try / catch
try { JsonConvert.DeserializeObject<T>(json); }
catch (JsonException ex) when (ex.Message.Contains("Both") && ex.Message.Contains("have"))
{
// remove the duplicate callback attribute
} Prevention
- Apply each On* attribute to exactly one method per type.
- Add a reflection test that asserts single-callback-per-attribute invariant.
When it happens
Trigger: Two methods in the same class both decorated with the same callback attribute, e.g. two [OnDeserialized] methods.
Common situations: Copy-pasting a callback method and forgetting to remove the original, or merging types during refactoring so a base and derived method both carry the same attribute.
Related errors
- Invalid Callback. Method '{3}' in type '{2}' has both '{0}'
- Virtual Method '{0}' of type '{1}' cannot be marked with '{2
- Serialization Callback '{1}' in type '{0}' must return void.
- Serialization Error Callback '{1}' in type '{0}' must have t
- Serialization Callback '{1}' in type '{0}' must have a singl
AI-assisted analysis of JamesNK/Newtonsoft.Json@4f73e74372 (2026-08-07).
Data as JSON: /api/errors/a45c6a17b86eacf2.
Report an issue: GitHub.