Skip to content

IgnoreSetNullValue ignored by specialized FieldReader subclasses (FieldReaderString et al.) #7618

@BartoszSiemienczuk

Description

@BartoszSiemienczuk

Summary

JSONReader.Feature.IgnoreSetNullValue is documented to skip setter calls when the incoming JSON value is null. It works for fields deserialized via FieldReaderObject, but is silently ignored for fields deserialized via the specialized subclasses — most commonly FieldReaderString, which is picked for every String field on a bean.

This means neither of the documented ways to enable the feature actually works for String fields:

  1. JSON.parseObject(json, Clazz.class, JSONReader.Feature.IgnoreSetNullValue) — parse-time context feature.
  2. @JSONType(deserializeFeatures = JSONReader.Feature.IgnoreSetNullValue) on the bean — annotation-level feature that correctly propagates into FieldReader.features.

I verified both against the current master (2.0.62-SNAPSHOT, commit 3697c2d37) and against 2.0.53.

Reproducer

public class Repro {
    @JSONType(deserializeFeatures = JSONReader.Feature.IgnoreSetNullValue)
    public static class Sample {
        private String name = "Default";
        public String getName() { return name; }
        public void setName(String n) { this.name = n; }
    }

    public static void main(String[] args) {
        Sample s = JSON.parseObject("{\"name\":null}", Sample.class);
        System.out.println(s.getName()); // expected "Default", actual null
    }
}

Also fails with the context-feature form:

JSON.parseObject("{\"name\":null}", Sample.class, JSONReader.Feature.IgnoreSetNullValue);
// expected name == "Default", actual null

Root cause

Debug-inspecting the generated reader confirms the feature does flow correctly into the FieldReader when @JSONType(deserializeFeatures=...) is used:

Reader class: com.alibaba.fastjson2.reader.ORG_1_1_Sample
FieldReader class: com.alibaba.fastjson2.reader.FieldReaderString
IgnoreSetNullValue in FieldReader.features? true   <-- correctly propagated
Parsed name = null                                  <-- but ignored at parse time

The propagation chain works:

  • ObjectReaderBaseModule.getBeanInfo reads @JSONType.deserializeFeatures and ORs them into beanInfo.readerFeatures.
  • ObjectReaderCreator ORs beanInfo.readerFeatures into fieldInfo.features.
  • The resulting FieldReaderString.features contains the IgnoreSetNullValue mask.

The feature is then ignored at the FieldReader subclass level. FieldReaderObject.accept(T, Object) correctly gates on it:

// FieldReaderObject.java:300
if (isParameter() || (value == null && (features & JSONReader.Feature.IgnoreSetNullValue.mask) != 0)) {
    return;
}

But FieldReaderString.accept(T, Object) and FieldReaderString.readFieldValue(JSONReader, T) do not check the feature — they call propertyAccessor.setObject(object, fieldValue) regardless of fieldValue == null:

// FieldReaderString.java:52-83  — no IgnoreSetNullValue guard
@Override
public void accept(T object, Object value) {
    String fieldValue = ...;
    ...
    propertyAccessor.setObject(object, fieldValue);
}

// FieldReaderString.java:87-     — no IgnoreSetNullValue guard
@Override
public void readFieldValue(JSONReader jsonReader, T object) {
    String fieldValue = jsonReader.readString();
    ...
    propertyAccessor.setObject(object, fieldValue);
}

A grep -n IgnoreSetNullValue core/src/main/java/.../reader/ shows the feature is honored in only a handful of readers: FieldReaderObject, FieldReaderZonedDateTime, FieldReaderLocalDateTime, FieldReaderInstant, FieldReaderStackTrace, plus ObjectReaderAdapter#createInstance(Map, features). All other specialized FieldReaders silently ignore it, including FieldReaderString and the primitive-boxed variants.

Suggested fix

Add the same null-skip gate used in FieldReaderObject to every specialized FieldReader subclass's accept(T, Object) and readFieldValue(JSONReader, T) paths. Minimal per-class diff, e.g. for FieldReaderString:

if (fieldValue == null && (features & JSONReader.Feature.IgnoreSetNullValue.mask) != 0) {
    return;
}

Environment

  • fastjson2: 2.0.53 and master (2.0.62-SNAPSHOT, commit 3697c2d37)
  • JDK: 21
  • OS: macOS

Context

Encountered while migrating a Spring FastJsonHttpMessageConverter-based service from Jackson (@JsonSetter(nulls = Nulls.SKIP)) to fastjson2. No combination of FastJsonConfig.setReaderFeatures, module-level BeanInfo.readerFeatures injection, or @JSONType(deserializeFeatures=...) restored Nulls.SKIP parity for String fields. Tracing confirmed the feature reaches the FieldReader correctly — it is simply not consulted by FieldReaderString.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions