JamesNK/Newtonsoft.Json · error · JsonException
Invalid Callback. Method '{3}' in type '{2}' has both '{0}'
Error message
Invalid Callback. Method '{3}' in type '{2}' has both '{0}' and '{1}'. What it means
A single method may not carry two different serialization callback attributes. IsValidCallback tracks the previously seen attribute type on the method and throws a JsonException (line 1322) if a second, different callback attribute is found on the same method. Each lifecycle event must map to a distinct method.
Source
Thrown at Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs:1322
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))
{
if (parameters == null || parameters.Length != 2 || parameters[0].ParameterType != typeof(StreamingContext) || parameters[1].ParameterType != typeof(ErrorContext))
{
throw new JsonException("Serialization Error Callback '{1}' in type '{0}' must have two parameters of type '{2}' and '{3}'.".FormatWith(CultureInfo.InvariantCulture, GetClrTypeFullName(method.DeclaringType!), method, typeof(StreamingContext), typeof(ErrorContext)));
}View on GitHub (pinned to 4f73e74372)
Solutions
- Split the logic into separate methods, each carrying a single callback attribute.
- Remove the extra attribute so only one lifecycle event is handled by the method.
Example fix
// before
public class Model {
[OnSerializing] [OnSerialized]
internal void OnLifecycle(StreamingContext c) {} // throws
}
// after
public class Model {
[OnSerializing] internal void OnSerializing(StreamingContext c) {}
[OnSerialized] internal void OnSerialized(StreamingContext c) {}
} Defensive patterns
Strategy: validation
Validate before calling
var attrs = new[] { typeof(OnSerializingAttribute), typeof(OnSerializedAttribute), typeof(OnDeserializingAttribute), typeof(OnDeserializedAttribute), typeof(OnErrorAttribute) };
foreach (var m in typeof(T).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
var count = attrs.Count(a => m.IsDefined(a, false));
if (count > 1)
throw new InvalidOperationException($"Method {m.Name} carries {count} callback attributes; only one allowed.");
} Type guard
static bool NoMethodHasMultipleCallbackAttributes(Type t)
{
var attrs = new[] { typeof(OnSerializingAttribute), typeof(OnSerializedAttribute), typeof(OnDeserializingAttribute), typeof(OnDeserializedAttribute), typeof(OnErrorAttribute) };
return t.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.All(m => attrs.Count(a => m.IsDefined(a, false)) <= 1);
} Try / catch
try { JsonConvert.DeserializeObject<T>(json); }
catch (JsonException ex) when (ex.Message.Contains("has both"))
{
// split the method so each callback attribute is on its own method
} Prevention
- Put only one callback attribute on any given method.
- Split combined lifecycle logic into separate single-attribute methods.
When it happens
Trigger: One method decorated with two callback attributes, e.g. [OnSerializing] and [OnSerialized] on the same method.
Common situations: Combining attributes to 'reuse' one method for multiple lifecycle hooks, or accidental double-attribute application.
Related errors
- Invalid attribute. Both '{0}' and '{1}' in type '{2}' have '
- 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/8a8ab16a04e4fc7a.
Report an issue: GitHub.