JamesNK/Newtonsoft.Json · error · ArgumentException

Property does not have a getter.

Error message

Property does not have a getter.

What it means

ExpressionReflectionDelegateFactory.CreateGet<T>(PropertyInfo) (ExpressionReflectionDelegateFactory.cs:228-261) compiles an expression tree to read a property. It calls propertyInfo.GetGetMethod(true); if the property has no get accessor (write-only), it throws ArgumentException at ExpressionReflectionDelegateFactory.cs:241. This is the Expression-based counterpart of the DynamicReflectionDelegateFactory error [251], used on platforms where Expression trees are preferred over ReflectionEmit (non-NET20/35).

Source

Thrown at Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs:241

                return () => (T)Activator.CreateInstance(type)!;
            }
        }

        [RequiresDynamicCode(MiscellaneousUtils.AotWarning)]
        public override Func<T, object?> CreateGet<T>(PropertyInfo propertyInfo)
        {
            ValidationUtils.ArgumentNotNull(propertyInfo, nameof(propertyInfo));

            Type instanceType = typeof(T);
            Type resultType = typeof(object);

            ParameterExpression parameterExpression = Expression.Parameter(instanceType, "instance");
            Expression resultExpression;

            MethodInfo? getMethod = propertyInfo.GetGetMethod(true);
            if (getMethod == null)
            {
                throw new ArgumentException("Property does not have a getter.");
            }

            if (getMethod.IsStatic)
            {
                resultExpression = Expression.MakeMemberAccess(null, propertyInfo);
            }
            else
            {
                Expression readParameter = EnsureCastExpression(parameterExpression, propertyInfo.DeclaringType!);

                resultExpression = Expression.MakeMemberAccess(readParameter, propertyInfo);
            }

            resultExpression = EnsureCastExpression(resultExpression, resultType);

            LambdaExpression lambdaExpression = Expression.Lambda(typeof(Func<T, object>), resultExpression, parameterExpression);

            Func<T, object?> compiled = (Func<T, object?>)lambdaExpression.Compile();

View on GitHub (pinned to 4f73e74372)

Solutions

  1. Add a getter to the property (private getters are accepted because GetGetMethod(true) is used).
  2. Annotate the write-only property with [JsonIgnore] so serialization skips it.
  3. Configure an IContractResolver that excludes properties where !CanRead.
  4. Expose the value through a separate readable member.

Example fix

// before
public string Secret { set; } // write-only -> throws on serialize
// after
[JsonIgnore]
public string Secret { set; }
Defensive patterns

Strategy: validation

Validate before calling

// Exclude write-only properties from serialization contracts.
foreach (var prop in type.GetProperties()) {
    if (prop.GetGetMethod(true) == null) continue; // write-only, skip
    /* build getter via ExpressionReflectionDelegateFactory */
}

Type guard

static bool HasGetter(System.Reflection.PropertyInfo p) => p.GetGetMethod(true) != null;

Try / catch

try { serializer.Serialize(writer, value); } catch (ArgumentException ex) when (ex.Message.Contains("does not have a getter")) { /* mark the offending property [JsonIgnore] */ }

Prevention

When it happens

Trigger: The serializer's contract creation attempts to build a getter for a write-only property (only a setter) on a runtime that selects ExpressionReflectionDelegateFactory.

Common situations: Write-only properties (set; only); fluent-builder APIs; generated proxies with asymmetric accessors; intent-restricted setters; same root cause as [251] but on a different delegate factory.

Related errors


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