{"record":{"id":"8a8ab16a04e4fc7a","repo":"JamesNK/Newtonsoft.Json","slug":"invalid-callback-method-3-in-type-2-has-b","errorCode":null,"errorMessage":"Invalid Callback. Method '{3}' in type '{2}' has both '{0}' and '{1}'.","messagePattern":"Invalid Callback\\. Method '(.+?)' in type '(.+?)' has both '(.+?)' and '(.+?)'\\.","errorType":"exception","errorClass":"JsonException","httpStatus":null,"severity":"error","filePath":"Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs","lineNumber":1322,"sourceCode":"\n            return false;\n        }\n\n        private static bool IsValidCallback(MethodInfo method, ParameterInfo[] parameters, Type attributeType, MethodInfo? currentCallback, ref Type? prevAttributeType)\n        {\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                }","sourceCodeStart":1304,"sourceCodeEnd":1340,"githubUrl":"https://github.com/JamesNK/Newtonsoft.Json/blob/4f73e74372445108d2c1bda37b36e6f5e43402e0/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs#L1304-L1340","documentation":"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.","triggerScenarios":"One method decorated with two callback attributes, e.g. [OnSerializing] and [OnSerialized] on the same method.","commonSituations":"Combining attributes to 'reuse' one method for multiple lifecycle hooks, or accidental double-attribute application.","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."],"exampleFix":"// before\npublic class Model {\n    [OnSerializing] [OnSerialized]\n    internal void OnLifecycle(StreamingContext c) {} // throws\n}\n\n// after\npublic class Model {\n    [OnSerializing] internal void OnSerializing(StreamingContext c) {}\n    [OnSerialized]   internal void OnSerialized(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    var count = attrs.Count(a => m.IsDefined(a, false));\n    if (count > 1)\n        throw new InvalidOperationException($\"Method {m.Name} carries {count} callback attributes; only one allowed.\");\n}","typeGuard":"static bool NoMethodHasMultipleCallbackAttributes(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        .All(m => attrs.Count(a => m.IsDefined(a, false)) <= 1);\n}","tryCatchPattern":"try { JsonConvert.DeserializeObject<T>(json); }\ncatch (JsonException ex) when (ex.Message.Contains(\"has both\"))\n{\n    // split the method so each callback attribute is on its own method\n}","preventionTips":["Put only one callback attribute on any given method.","Split combined lifecycle logic into separate single-attribute methods."],"tags":["callback","attribute-validation","contract-resolution"],"analyzedSha":"4f73e74372445108d2c1bda37b36e6f5e43402e0","analyzedAt":"2026-08-07T06:10:08.596Z","schemaVersion":2},"datasetVersion":"2026-08-07T07:17:06.508Z"}