diff --git a/core/src/main/java/com/alibaba/fastjson2/reader/FieldReader.java b/core/src/main/java/com/alibaba/fastjson2/reader/FieldReader.java index 13f11f2f89..c69a45bcef 100644 --- a/core/src/main/java/com/alibaba/fastjson2/reader/FieldReader.java +++ b/core/src/main/java/com/alibaba/fastjson2/reader/FieldReader.java @@ -746,4 +746,16 @@ private boolean needCompareToActualFieldClass(Class clazz) { public final boolean isParameter() { return paramNameHash != 0; } + + protected final boolean ignoreSetNullValue() { + return (features & JSONReader.Feature.IgnoreSetNullValue.mask) != 0; + } + + protected final boolean ignoreSetNullValue(JSONReader jsonReader) { + long allFeatures = features; + if (jsonReader != null) { + allFeatures |= jsonReader.getContext().getFeatures(); + } + return (allFeatures & JSONReader.Feature.IgnoreSetNullValue.mask) != 0; + } } diff --git a/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderBigDecimal.java b/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderBigDecimal.java index 821698d632..5fda0ee730 100644 --- a/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderBigDecimal.java +++ b/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderBigDecimal.java @@ -29,6 +29,9 @@ public FieldReaderBigDecimal( @Override public void accept(T object, Object value) { + if (value == null && ignoreSetNullValue()) { + return; + } propertyAccessor.setObject(object, value); } @@ -45,6 +48,10 @@ public void readFieldValue(JSONReader jsonReader, T object) { } } + if (fieldValue == null && ignoreSetNullValue(jsonReader)) { + return; + } + propertyAccessor.setObject(object, fieldValue); } diff --git a/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderBigInteger.java b/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderBigInteger.java index 94058e9340..d44a707ec6 100644 --- a/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderBigInteger.java +++ b/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderBigInteger.java @@ -41,11 +41,17 @@ public void readFieldValue(JSONReader jsonReader, T object) { throw ex; } } + if (value == null && ignoreSetNullValue(jsonReader)) { + return; + } propertyAccessor.setObject(object, value); } @Override public void accept(T object, Object value) { + if (value == null && ignoreSetNullValue()) { + return; + } propertyAccessor.setObject(object, value); } diff --git a/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderBool.java b/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderBool.java index 8ed439526c..626c5d39bd 100644 --- a/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderBool.java +++ b/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderBool.java @@ -32,6 +32,9 @@ public FieldReaderBool( @Override public void accept(T object, Object value) { + if (value == null && ignoreSetNullValue()) { + return; + } propertyAccessor.setObject(object, TypeUtils.toBoolean(value)); } @@ -49,6 +52,10 @@ public void readFieldValue(JSONReader jsonReader, T object) { } } + if (fieldValue == null && ignoreSetNullValue(jsonReader)) { + return; + } + propertyAccessor.setObject(object, fieldValue); } diff --git a/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderDate.java b/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderDate.java index 647ab69afe..d862661469 100644 --- a/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderDate.java +++ b/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderDate.java @@ -64,11 +64,17 @@ public void readFieldValue(JSONReader jsonReader, T object) { } date = null; } + if (date == null && ignoreSetNullValue(jsonReader)) { + return; + } accept(object, date); } @Override protected void accept(T object, Date value) { + if (value == null && ignoreSetNullValue()) { + return; + } propertyAccessor.setObject(object, value); } diff --git a/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderDouble.java b/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderDouble.java index 2b80047789..3b3375374e 100644 --- a/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderDouble.java +++ b/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderDouble.java @@ -31,6 +31,9 @@ final class FieldReaderDouble @Override public void accept(T object, Object value) { + if (value == null && ignoreSetNullValue()) { + return; + } propertyAccessor.setObject(object, value); } @@ -51,6 +54,10 @@ public void readFieldValue(JSONReader jsonReader, T object) { return; } + if (value == null && ignoreSetNullValue(jsonReader)) { + return; + } + propertyAccessor.setObject(object, value); } diff --git a/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderFloat.java b/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderFloat.java index c4bbb622c4..ac7d5adbcd 100644 --- a/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderFloat.java +++ b/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderFloat.java @@ -31,6 +31,9 @@ final class FieldReaderFloat @Override public void accept(T object, Object value) { + if (value == null && ignoreSetNullValue()) { + return; + } propertyAccessor.setObject(object, value); } @@ -47,6 +50,10 @@ public void readFieldValue(JSONReader jsonReader, T object) { } } + if (value == null && ignoreSetNullValue(jsonReader)) { + return; + } + propertyAccessor.setObject(object, value); } diff --git a/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderInt16.java b/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderInt16.java index 2e5cf56a25..af8241e8c8 100644 --- a/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderInt16.java +++ b/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderInt16.java @@ -31,6 +31,9 @@ final class FieldReaderInt16 @Override public void accept(T object, Object value) { + if (value == null && ignoreSetNullValue()) { + return; + } propertyAccessor.setObject(object, value); } @@ -48,6 +51,10 @@ public void readFieldValue(JSONReader jsonReader, T object) { } } + if (value == null && ignoreSetNullValue(jsonReader)) { + return; + } + propertyAccessor.setObject(object, value); } diff --git a/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderInt32.java b/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderInt32.java index 6c68c2e004..89f44f30d8 100644 --- a/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderInt32.java +++ b/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderInt32.java @@ -31,6 +31,9 @@ final class FieldReaderInt32 @Override public void accept(T object, Object value) { + if (value == null && ignoreSetNullValue()) { + return; + } propertyAccessor.setObject(object, value); } @@ -47,6 +50,10 @@ public void readFieldValue(JSONReader jsonReader, T object) { } } + if (value == null && ignoreSetNullValue(jsonReader)) { + return; + } + propertyAccessor.setObject(object, value); } diff --git a/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderInt64.java b/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderInt64.java index 15c02beb4c..0e46d03047 100644 --- a/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderInt64.java +++ b/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderInt64.java @@ -32,6 +32,10 @@ final class FieldReaderInt64 @Override public void accept(T object, Object value) { + if (value == null && ignoreSetNullValue()) { + return; + } + Long longValue = TypeUtils.toLong(value); if (schema != null) { @@ -54,6 +58,10 @@ public void readFieldValue(JSONReader jsonReader, T object) { } } + if (value == null && ignoreSetNullValue(jsonReader)) { + return; + } + if (schema != null) { schema.assertValidate(value); } diff --git a/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderInt8.java b/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderInt8.java index 5fcd59d0b0..b8578567d4 100644 --- a/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderInt8.java +++ b/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderInt8.java @@ -31,6 +31,9 @@ final class FieldReaderInt8 @Override public void accept(T object, Object value) { + if (value == null && ignoreSetNullValue()) { + return; + } propertyAccessor.setObject(object, value); } @@ -48,6 +51,10 @@ public void readFieldValue(JSONReader jsonReader, T object) { } } + if (value == null && ignoreSetNullValue(jsonReader)) { + return; + } + propertyAccessor.setObject(object, value); } diff --git a/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderNumber.java b/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderNumber.java index c5f44a8a62..8798156b9b 100644 --- a/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderNumber.java +++ b/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderNumber.java @@ -28,6 +28,9 @@ public FieldReaderNumber( @Override public void accept(T object, Object value) { + if (value == null && ignoreSetNullValue()) { + return; + } propertyAccessor.setObject(object, value); } @@ -54,6 +57,10 @@ public void readFieldValue(JSONReader jsonReader, T object) { } } + if (fieldValue == null && ignoreSetNullValue(jsonReader)) { + return; + } + propertyAccessor.setObject(object, fieldValue); } diff --git a/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderString.java b/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderString.java index 464e137e16..42e6f1eddd 100644 --- a/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderString.java +++ b/core/src/main/java/com/alibaba/fastjson2/reader/FieldReaderString.java @@ -69,6 +69,10 @@ public void accept(T object, Object value) { } } + if (fieldValue == null && ignoreSetNullValue()) { + return; + } + if (schema != null) { schema.assertValidate(fieldValue); } @@ -98,6 +102,10 @@ public void readFieldValue(JSONReader jsonReader, T object) { } } + if (fieldValue == null && ignoreSetNullValue(jsonReader)) { + return; + } + if (schema != null) { schema.assertValidate(fieldValue); } @@ -127,6 +135,10 @@ public void readFieldValueJSONB(JSONReader jsonReader, T object) { } } + if (fieldValue == null && ignoreSetNullValue(jsonReader)) { + return; + } + if (schema != null) { schema.assertValidate(fieldValue); } diff --git a/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderCreatorASM.java b/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderCreatorASM.java index 8ea085ee78..11a9b29229 100644 --- a/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderCreatorASM.java +++ b/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderCreatorASM.java @@ -3014,7 +3014,9 @@ private void genReadFieldValue( MethodWriter mw = mwc.mw; - if ((fieldFeatures & JSONReader.Feature.NullOnError.mask) != 0) { + if ((fieldFeatures & JSONReader.Feature.NullOnError.mask) != 0 + || ((fieldFeatures & JSONReader.Feature.IgnoreSetNullValue.mask) != 0 + && !(context.objectReaderAdapter instanceof ObjectReaderNoneDefaultConstructor))) { mw.aload(THIS); mw.getfield(classNameType, fieldReader(fieldReaderIndex), DESC_FIELD_READER); mw.aload(JSON_READER); @@ -3028,6 +3030,31 @@ private void genReadFieldValue( Label endSet_ = new Label(); + // Honor JSONReader.Feature.IgnoreSetNullValue passed at parse time (context-level). + // Annotation-level IgnoreSetNullValue is handled by the fieldFeatures branch above. + // NoneDefaultConstructor uses temp locals (no OBJECT slot yet), so skip — that + // path needs a different fix. + if (!fieldClass.isPrimitive() + && !(context.objectReaderAdapter instanceof ObjectReaderNoneDefaultConstructor)) { + // if ((FEATURES & IgnoreSetNullValue.mask) != 0) { fieldReader.readFieldValue(jsonReader, object); goto endSet_; } + Label noFallback_ = new Label(); + mw.lload(FEATURES); + mw.visitLdcInsn(JSONReader.Feature.IgnoreSetNullValue.mask); + mw.land(); + mw.lconst_0(); + mw.lcmp(); + mw.ifeq(noFallback_); + + mw.aload(THIS); + mw.getfield(classNameType, fieldReader(fieldReaderIndex), DESC_FIELD_READER); + mw.aload(JSON_READER); + mw.aload(OBJECT); + mw.invokevirtual(TYPE_FIELD_READE, "readFieldValue", METHOD_DESC_READ_FIELD_VALUE); + mw.goto_(endSet_); + + mw.visitLabel(noFallback_); + } + String TYPE_FIELD_CLASS = ASMUtils.type(fieldClass); String DESC_FIELD_CLASS = ASMUtils.desc(fieldClass); diff --git a/core/src/test/java/com/alibaba/fastjson2/issues_7600/Issue7618.java b/core/src/test/java/com/alibaba/fastjson2/issues_7600/Issue7618.java new file mode 100644 index 0000000000..15c13a383b --- /dev/null +++ b/core/src/test/java/com/alibaba/fastjson2/issues_7600/Issue7618.java @@ -0,0 +1,188 @@ +package com.alibaba.fastjson2.issues_7600; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONB; +import com.alibaba.fastjson2.JSONReader; +import com.alibaba.fastjson2.annotation.JSONType; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Date; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Issue 7618: JSONReader.Feature.IgnoreSetNullValue is silently ignored by + * specialized FieldReader subclasses (FieldReaderString, FieldReaderInt32 etc.) + * because the null-skip gate present in FieldReaderObject was not duplicated + * into them. The feature must work both via @JSONType.deserializeFeatures and + * via JSONReader.Feature passed at parse time. + */ +public class Issue7618 { + @JSONType(deserializeFeatures = JSONReader.Feature.IgnoreSetNullValue) + public static class StringSample { + private String name = "Default"; + + public String getName() { + return name; + } + + public void setName(String n) { + this.name = n; + } + } + + @Test + public void testStringAnnotation() { + StringSample s = JSON.parseObject("{\"name\":null}", StringSample.class); + assertEquals("Default", s.getName()); + } + + @Test + public void testStringJSONBAnnotation() { + java.util.Map map = new java.util.HashMap<>(); + map.put("name", null); + byte[] bytes = JSONB.toBytes(map); + StringSample s = JSONB.parseObject(bytes, StringSample.class); + assertEquals("Default", s.getName()); + } + + public static class StringSamplePlain { + private String name = "Default"; + + public String getName() { + return name; + } + + public void setName(String n) { + this.name = n; + } + } + + @Test + public void testStringContextFeature() { + StringSamplePlain s = JSON.parseObject( + "{\"name\":null}", + StringSamplePlain.class, + JSONReader.Feature.IgnoreSetNullValue); + assertEquals("Default", s.getName()); + } + + @JSONType(deserializeFeatures = JSONReader.Feature.IgnoreSetNullValue) + public static class BoxedSample { + private Integer i = 42; + private Long l = 100L; + private Boolean b = Boolean.TRUE; + private BigDecimal bd = new BigDecimal("3.14"); + private BigInteger bi = BigInteger.TEN; + private Date d = new Date(0L); + private Float f = 1.5f; + private Double db = 2.5d; + private Short sh = 7; + private Byte by = 3; + private Number n = 99; + + public Integer getI() { return i; } + public Long getL() { return l; } + public Boolean getB() { return b; } + public BigDecimal getBd() { return bd; } + public BigInteger getBi() { return bi; } + public Date getD() { return d; } + public Float getF() { return f; } + public Double getDb() { return db; } + public Short getSh() { return sh; } + public Byte getBy() { return by; } + public Number getN() { return n; } + + public void setI(Integer i) { this.i = i; } + public void setL(Long l) { this.l = l; } + public void setB(Boolean b) { this.b = b; } + public void setBd(BigDecimal bd) { this.bd = bd; } + public void setBi(BigInteger bi) { this.bi = bi; } + public void setD(Date d) { this.d = d; } + public void setF(Float f) { this.f = f; } + public void setDb(Double db) { this.db = db; } + public void setSh(Short sh) { this.sh = sh; } + public void setBy(Byte by) { this.by = by; } + public void setN(Number n) { this.n = n; } + } + + @Test + public void testBoxedAnnotation() { + BoxedSample s = JSON.parseObject( + "{\"i\":null,\"l\":null,\"b\":null,\"bd\":null,\"bi\":null,\"d\":null," + + "\"f\":null,\"db\":null,\"sh\":null,\"by\":null,\"n\":null}", + BoxedSample.class); + assertEquals(Integer.valueOf(42), s.getI()); + assertEquals(Long.valueOf(100L), s.getL()); + assertEquals(Boolean.TRUE, s.getB()); + assertEquals(new BigDecimal("3.14"), s.getBd()); + assertEquals(BigInteger.TEN, s.getBi()); + assertEquals(new Date(0L), s.getD()); + assertEquals(Float.valueOf(1.5f), s.getF()); + assertEquals(Double.valueOf(2.5d), s.getDb()); + assertEquals(Short.valueOf((short) 7), s.getSh()); + assertEquals(Byte.valueOf((byte) 3), s.getBy()); + assertEquals(99, s.getN()); + } + + public static class BoxedSamplePlain { + private Integer i = 42; + private Long l = 100L; + private Boolean b = Boolean.TRUE; + private String s = "kept"; + private Float f = 1.5f; + private Double db = 2.5d; + private Short sh = 7; + private Byte by = 3; + private Date d = new Date(0L); + + public Integer getI() { return i; } + public Long getL() { return l; } + public Boolean getB() { return b; } + public String getS() { return s; } + public Float getF() { return f; } + public Double getDb() { return db; } + public Short getSh() { return sh; } + public Byte getBy() { return by; } + public Date getD() { return d; } + + public void setI(Integer i) { this.i = i; } + public void setL(Long l) { this.l = l; } + public void setB(Boolean b) { this.b = b; } + public void setS(String s) { this.s = s; } + public void setF(Float f) { this.f = f; } + public void setDb(Double db) { this.db = db; } + public void setSh(Short sh) { this.sh = sh; } + public void setBy(Byte by) { this.by = by; } + public void setD(Date d) { this.d = d; } + } + + @Test + public void testBoxedContextFeature() { + BoxedSamplePlain s = JSON.parseObject( + "{\"i\":null,\"l\":null,\"b\":null,\"s\":null," + + "\"f\":null,\"db\":null,\"sh\":null,\"by\":null,\"d\":null}", + BoxedSamplePlain.class, + JSONReader.Feature.IgnoreSetNullValue); + assertEquals(Integer.valueOf(42), s.getI()); + assertEquals(Long.valueOf(100L), s.getL()); + assertEquals(Boolean.TRUE, s.getB()); + assertEquals("kept", s.getS()); + assertEquals(Float.valueOf(1.5f), s.getF()); + assertEquals(Double.valueOf(2.5d), s.getDb()); + assertEquals(Short.valueOf((short) 7), s.getSh()); + assertEquals(Byte.valueOf((byte) 3), s.getBy()); + assertEquals(new Date(0L), s.getD()); + } + + @Test + public void testNonNullValueStillSet() { + StringSamplePlain s = JSON.parseObject( + "{\"name\":\"hello\"}", + StringSamplePlain.class, + JSONReader.Feature.IgnoreSetNullValue); + assertEquals("hello", s.getName()); + } +}