JamesNK/Newtonsoft.Json · error · JsonException

Invalid extension data attribute on '{0}'. Member '{1}' type

Error message

Invalid extension data attribute on '{0}'. Member '{1}' type must implement IDictionary<string, JToken>.

What it means

The [JsonExtensionData] member's type must implement IDictionary<string, JToken> (key assignable from string, value assignable from JToken). Contract resolution checks the generic dictionary arguments and throws a JsonException (line 488) if the member is any other collection/value type. Extension data is stored as JToken values keyed by string, so incompatible dictionary types are rejected.

Source

Thrown at Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs:488

                if (!ReflectionUtils.CanReadMemberValue(m, true))
                {
                    throw new JsonException("Invalid extension data attribute on '{0}'. Member '{1}' must have a getter.".FormatWith(CultureInfo.InvariantCulture, GetClrTypeFullName(m.DeclaringType!), m.Name));
                }

                Type t = ReflectionUtils.GetMemberUnderlyingType(m);

                if (ReflectionUtils.ImplementsGenericDefinition(t, typeof(IDictionary<,>), out Type? dictionaryType))
                {
                    Type keyType = dictionaryType.GetGenericArguments()[0];
                    Type valueType = dictionaryType.GetGenericArguments()[1];

                    if (keyType.IsAssignableFrom(typeof(string)) && valueType.IsAssignableFrom(typeof(JToken)))
                    {
                        return true;
                    }
                }

                throw new JsonException("Invalid extension data attribute on '{0}'. Member '{1}' type must implement IDictionary<string, JToken>.".FormatWith(CultureInfo.InvariantCulture, GetClrTypeFullName(m.DeclaringType!), m.Name));
            });

            return extensionDataMember;
        }

        private static void SetExtensionDataDelegates(JsonObjectContract contract, MemberInfo member)
        {
            JsonExtensionDataAttribute? extensionDataAttribute = ReflectionUtils.GetAttribute<JsonExtensionDataAttribute>(member);
            if (extensionDataAttribute == null)
            {
                return;
            }

            Type t = ReflectionUtils.GetMemberUnderlyingType(member);

            ReflectionUtils.ImplementsGenericDefinition(t, typeof(IDictionary<,>), out Type? dictionaryType);

            Type keyType = dictionaryType!.GetGenericArguments()[0];

View on GitHub (pinned to 4f73e74372)

Solutions

  1. Change the member type to IDictionary<string, JToken> or Dictionary<string, JToken>.
  2. If you need strongly-typed values, deserialize into JToken and convert in an OnDeserialized callback rather than changing the dictionary type.

Example fix

// before
[JsonExtensionData]
public Dictionary<string, object> Extra { get; set; } // wrong value type

// after
[JsonExtensionData]
public Dictionary<string, JToken> Extra { get; set; } = new Dictionary<string, JToken>();
Defensive patterns

Strategy: validation

Validate before calling

foreach (var p in typeof(T).GetProperties().Where(p => p.IsDefined(typeof(JsonExtensionDataAttribute), false)))
{
    var t = p.PropertyType;
    var ok = t.GetInterfaces().Concat(new[] { t })
        .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDictionary<,>)
            && i.GetGenericArguments()[0] == typeof(string)
            && typeof(JToken).IsAssignableFrom(i.GetGenericArguments()[1]));
    if (!ok) throw new InvalidOperationException($"{p.Name} must be IDictionary<string, JToken>.");
}

Type guard

static bool IsValidExtensionDataType(Type t)
{
    return t.GetInterfaces().Concat(new[] { t }).Any(i =>
        i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDictionary<,>)
        && i.GetGenericArguments()[0] == typeof(string)
        && typeof(JToken).IsAssignableFrom(i.GetGenericArguments()[1]));
}

Try / catch

try { JsonConvert.DeserializeObject<T>(json); }
catch (JsonException ex) when (ex.Message.Contains("IDictionary<string, JToken>"))
{
    // change the member type to IDictionary<string, JToken>
}

Prevention

When it happens

Trigger: Decorating a member of the wrong type, e.g. [JsonExtensionData] Dictionary<string, object>, List<KeyValuePair<...>>, Dictionary<int, JToken>, or a non-dictionary field.

Common situations: Assuming [JsonExtensionData] accepts Dictionary<string, object> (it does not), or copy-pasting the attribute onto an unrelated collection member.

Related errors


AI-assisted analysis of JamesNK/Newtonsoft.Json@4f73e74372 (2026-08-07). Data as JSON: /api/errors/78e1975add315895. Report an issue: GitHub.