JamesNK/Newtonsoft.Json · error · JsonException
Virtual Method '{0}' of type '{1}' cannot be marked with '{2
Error message
Virtual Method '{0}' of type '{1}' cannot be marked with '{2}' attribute. What it means
Serialization callback attributes cannot be applied to virtual methods. IsValidCallback checks method.IsVirtual and throws a JsonException (line 1327). Virtual callbacks are rejected because a derived override could change the signature/behavior in ways the contract resolver cannot statically validate, breaking reliable serialization lifecycle hooks.
Source
Thrown at Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs:1327
{
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)));
}
}
else
{
if (parameters == null || parameters.Length != 1 || parameters[0].ParameterType != typeof(StreamingContext))
{View on GitHub (pinned to 4f73e74372)
Solutions
- Make the callback method non-virtual (remove 'virtual'/'override').
- If a derived type needs its own hook, give the derived type its own private non-virtual [OnDeserialized] method instead of overriding the base one.
Example fix
// before
public class Model {
[OnDeserialized]
protected virtual void OnDeserialized(StreamingContext c) {} // throws
}
// after
public class Model {
[OnDeserialized]
protected 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 m in typeof(T).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
if (m.IsVirtual && attrs.Any(a => m.IsDefined(a, false)))
throw new InvalidOperationException($"Virtual method {m.Name} cannot carry a serialization callback attribute.");
} Type guard
static bool NoVirtualCallbackMethods(Type t)
{
var attrs = new[] { typeof(OnSerializingAttribute), typeof(OnSerializedAttribute), typeof(OnDeserializingAttribute), typeof(OnDeserializedAttribute), typeof(OnErrorAttribute) };
return t.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where(m => attrs.Any(a => m.IsDefined(a, false)))
.All(m => !m.IsVirtual);
} Try / catch
try { JsonConvert.DeserializeObject<T>(json); }
catch (JsonException ex) when (ex.Message.Contains("cannot be marked with"))
{
// make the callback method non-virtual
} Prevention
- Keep callback methods non-virtual.
- Give derived types their own private non-virtual callbacks rather than overriding base ones.
When it happens
Trigger: Decorating a 'virtual' (or 'override') method with any of the On* callback attributes.
Common situations: Adding [OnDeserialized] to an override of a base virtual method, or marking the callback virtual so subclasses can extend it.
Related errors
- Invalid attribute. Both '{0}' and '{1}' in type '{2}' have '
- Invalid Callback. Method '{3}' in type '{2}' has both '{0}'
- 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/1c4fe41c4fe8d94d.
Report an issue: GitHub.