{"record":{"id":"1c4fe41c4fe8d94d","repo":"JamesNK/Newtonsoft.Json","slug":"virtual-method-0-of-type-1-cannot-be-marke","errorCode":null,"errorMessage":"Virtual Method '{0}' of type '{1}' cannot be marked with '{2}' attribute.","messagePattern":"Virtual Method '(.+?)' of type '(.+?)' cannot be marked with '(.+?)' attribute\\.","errorType":"exception","errorClass":"JsonException","httpStatus":null,"severity":"error","filePath":"Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs","lineNumber":1327,"sourceCode":"        {\n            if (!method.IsDefined(attributeType, false))\n            {\n                return false;\n            }\n\n            if (currentCallback != null)\n            {\n                throw new JsonException(\"Invalid attribute. Both '{0}' and '{1}' in type '{2}' have '{3}'.\".FormatWith(CultureInfo.InvariantCulture, method, currentCallback, GetClrTypeFullName(method.DeclaringType!), attributeType));\n            }\n\n            if (prevAttributeType != null)\n            {\n                throw new JsonException(\"Invalid Callback. Method '{3}' in type '{2}' has both '{0}' and '{1}'.\".FormatWith(CultureInfo.InvariantCulture, prevAttributeType, attributeType, GetClrTypeFullName(method.DeclaringType!), method));\n            }\n\n            if (method.IsVirtual)\n            {\n                throw new JsonException(\"Virtual Method '{0}' of type '{1}' cannot be marked with '{2}' attribute.\".FormatWith(CultureInfo.InvariantCulture, method, GetClrTypeFullName(method.DeclaringType!), attributeType));\n            }\n\n            if (method.ReturnType != typeof(void))\n            {\n                throw new JsonException(\"Serialization Callback '{1}' in type '{0}' must return void.\".FormatWith(CultureInfo.InvariantCulture, GetClrTypeFullName(method.DeclaringType!), method));\n            }\n\n            if (attributeType == typeof(OnErrorAttribute))\n            {\n                if (parameters == null || parameters.Length != 2 || parameters[0].ParameterType != typeof(StreamingContext) || parameters[1].ParameterType != typeof(ErrorContext))\n                {\n                    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)));\n                }\n            }\n            else\n            {\n                if (parameters == null || parameters.Length != 1 || parameters[0].ParameterType != typeof(StreamingContext))\n                {","sourceCodeStart":1309,"sourceCodeEnd":1345,"githubUrl":"https://github.com/JamesNK/Newtonsoft.Json/blob/4f73e74372445108d2c1bda37b36e6f5e43402e0/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs#L1309-L1345","documentation":"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.","triggerScenarios":"Decorating a 'virtual' (or 'override') method with any of the On* callback attributes.","commonSituations":"Adding [OnDeserialized] to an override of a base virtual method, or marking the callback virtual so subclasses can extend it.","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."],"exampleFix":"// before\npublic class Model {\n    [OnDeserialized]\n    protected virtual void OnDeserialized(StreamingContext c) {} // throws\n}\n\n// after\npublic class Model {\n    [OnDeserialized]\n    protected void OnDeserialized(StreamingContext c) {}\n}","handlingStrategy":"validation","validationCode":"var attrs = new[] { typeof(OnSerializingAttribute), typeof(OnSerializedAttribute), typeof(OnDeserializingAttribute), typeof(OnDeserializedAttribute), typeof(OnErrorAttribute) };\nforeach (var m in typeof(T).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))\n{\n    if (m.IsVirtual && attrs.Any(a => m.IsDefined(a, false)))\n        throw new InvalidOperationException($\"Virtual method {m.Name} cannot carry a serialization callback attribute.\");\n}","typeGuard":"static bool NoVirtualCallbackMethods(Type t)\n{\n    var attrs = new[] { typeof(OnSerializingAttribute), typeof(OnSerializedAttribute), typeof(OnDeserializingAttribute), typeof(OnDeserializedAttribute), typeof(OnErrorAttribute) };\n    return t.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)\n        .Where(m => attrs.Any(a => m.IsDefined(a, false)))\n        .All(m => !m.IsVirtual);\n}","tryCatchPattern":"try { JsonConvert.DeserializeObject<T>(json); }\ncatch (JsonException ex) when (ex.Message.Contains(\"cannot be marked with\"))\n{\n    // make the callback method non-virtual\n}","preventionTips":["Keep callback methods non-virtual.","Give derived types their own private non-virtual callbacks rather than overriding base ones."],"tags":["callback","attribute-validation","contract-resolution","virtual-method"],"analyzedSha":"4f73e74372445108d2c1bda37b36e6f5e43402e0","analyzedAt":"2026-08-07T06:10:08.596Z","schemaVersion":2},"datasetVersion":"2026-08-07T07:17:06.508Z"}