Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a JSONReader.Feature that can convert empty string to null #2321

Merged
merged 3 commits into from
Mar 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion core/src/main/java/com/alibaba/fastjson2/JSONReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -4418,7 +4418,13 @@ public enum Feature {
/**
* @since 2.0.42
*/
ErrorOnUnknownProperties(1 << 26);
ErrorOnUnknownProperties(1 << 26),

/**
* empty string "" convert to null
* since 2.0.48
*/
EmptyStringAsNull(1 << 27);

public final long mask;

Expand Down
4 changes: 4 additions & 0 deletions core/src/main/java/com/alibaba/fastjson2/JSONReaderASCII.java
Original file line number Diff line number Diff line change
Expand Up @@ -1541,6 +1541,10 @@ public String readString() {
if ((context.features & Feature.TrimString.mask) != 0) {
str = str.trim();
}
// empty string to null
if (str.isEmpty() && (context.features & Feature.EmptyStringAsNull.mask) != 0) {
str = null;
}

int ch = ++offset == end ? EOI : bytes[offset++];
while (ch <= ' ' && (1L << ch & SPACE) != 0) {
Expand Down
36 changes: 36 additions & 0 deletions core/src/main/java/com/alibaba/fastjson2/JSONReaderJSONB.java
Original file line number Diff line number Diff line change
Expand Up @@ -1011,6 +1011,10 @@ public Object readAny() {
if ((context.features & Feature.TrimString.mask) != 0) {
str = str.trim();
}
// empty string to null
if (str.isEmpty() && (context.features & Feature.EmptyStringAsNull.mask) != 0) {
str = null;
}
return str;
} else if (STRING_CREATOR_JDK11 != null) {
byte[] chars = new byte[strlen];
Expand All @@ -1021,6 +1025,10 @@ public Object readAny() {
if ((context.features & Feature.TrimString.mask) != 0) {
str = str.trim();
}
// empty string to null
if (str.isEmpty() && (context.features & Feature.EmptyStringAsNull.mask) != 0) {
str = null;
}
return str;
}

Expand All @@ -1030,6 +1038,10 @@ public Object readAny() {
if ((context.features & Feature.TrimString.mask) != 0) {
str = str.trim();
}
// empty string to null
if (str.isEmpty() && (context.features & Feature.EmptyStringAsNull.mask) != 0) {
str = null;
}
return str;
}

Expand Down Expand Up @@ -2993,6 +3005,10 @@ public String readString() {
if ((context.features & Feature.TrimString.mask) != 0) {
str = str.trim();
}
// empty string to null
if (str.isEmpty() && (context.features & Feature.EmptyStringAsNull.mask) != 0) {
str = null;
}
return str;
}
}
Expand Down Expand Up @@ -3033,6 +3049,10 @@ private String readStringNonAscii() {
if ((context.features & Feature.TrimString.mask) != 0) {
str = str.trim();
}
// empty string to null
if (str.isEmpty() && (context.features & Feature.EmptyStringAsNull.mask) != 0) {
str = null;
}
return str;
}

Expand Down Expand Up @@ -3068,6 +3088,10 @@ private String readString(Charset charset) {
if ((context.features & Feature.TrimString.mask) != 0) {
str = str.trim();
}
// empty string to null
if (str.isEmpty() && (context.features & Feature.EmptyStringAsNull.mask) != 0) {
str = null;
}

return str;
}
Expand All @@ -3094,6 +3118,10 @@ private String readUTF16BE() {
if ((context.features & Feature.TrimString.mask) != 0) {
str = str.trim();
}
// empty string to null
if (str.isEmpty() && (context.features & Feature.EmptyStringAsNull.mask) != 0) {
str = null;
}

return str;
}
Expand Down Expand Up @@ -3128,6 +3156,10 @@ private String readUTF16LE() {
if ((context.features & Feature.TrimString.mask) != 0) {
str = str.trim();
}
// empty string to null
if (str.isEmpty() && (context.features & Feature.EmptyStringAsNull.mask) != 0) {
str = null;
}
return str;
}
return null;
Expand Down Expand Up @@ -3170,6 +3202,10 @@ private String readStringUTF8() {
if ((context.features & Feature.TrimString.mask) != 0) {
str = str.trim();
}
// empty string to null
if (str.isEmpty() && (context.features & Feature.EmptyStringAsNull.mask) != 0) {
str = null;
}

return str;
}
Expand Down
4 changes: 4 additions & 0 deletions core/src/main/java/com/alibaba/fastjson2/JSONReaderUTF16.java
Original file line number Diff line number Diff line change
Expand Up @@ -3513,6 +3513,10 @@ public String readString() {
if ((context.features & Feature.TrimString.mask) != 0) {
str = str.trim();
}
// empty string to null
if (str.isEmpty() && (context.features & Feature.EmptyStringAsNull.mask) != 0) {
str = null;
}

int ch = ++offset == end ? EOI : chars[offset++];
while (ch <= ' ' && (1L << ch & SPACE) != 0) {
Expand Down
4 changes: 4 additions & 0 deletions core/src/main/java/com/alibaba/fastjson2/JSONReaderUTF8.java
Original file line number Diff line number Diff line change
Expand Up @@ -5276,6 +5276,10 @@ public String readString() {
if ((context.features & Feature.TrimString.mask) != 0) {
str = str.trim();
}
// empty string to null
if (str.isEmpty() && (context.features & Feature.EmptyStringAsNull.mask) != 0) {
str = null;
}

int ch = ++offset == end ? EOI : bytes[offset++];
while (ch <= ' ' && (1L << ch & SPACE) != 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
class FieldReaderStringField<T>
extends FieldReaderObjectField<T> {
final boolean trim;
final boolean emptyToNull;

FieldReaderStringField(String fieldName, Class fieldType, int ordinal, long features, String format, String defaultValue, JSONSchema schema, Field field) {
super(fieldName, fieldType, fieldType, ordinal, features, format, defaultValue, schema, field);
trim = "trim".equals(format) || (features & JSONReader.Feature.TrimString.mask) != 0;
emptyToNull = (features & JSONReader.Feature.EmptyStringAsNull.mask) != 0;
}

@Override
Expand Down Expand Up @@ -69,7 +71,10 @@ public void accept(T object, Object value) {
if (trim && fieldValue != null) {
fieldValue = fieldValue.trim();
}

// empty string to null
if (emptyToNull && fieldValue != null && fieldValue.isEmpty()) {
fieldValue = null;
}
if (schema != null) {
schema.assertValidate(fieldValue);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,12 +182,15 @@ public Object readObject(JSONReader jsonReader, Type fieldType, Object fieldName
if (ch == '[') {
jsonReader.next();
while (!jsonReader.nextIfArrayEnd()) {
list.add(
jsonReader.readString());
String str = jsonReader.readString();
if (str == null) {
continue;
}
list.add(str);
}
} else if (ch == '"' || ch == '\'' || ch == '{') {
String str = jsonReader.readString();
if (!str.isEmpty()) {
if (str != null && !str.isEmpty()) {
list.add(str);
}
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package com.alibaba.fastjson2.features;

import com.alibaba.fastjson2.*;
import lombok.Data;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.io.ByteArrayInputStream;
import java.util.*;

/**
* @author 张治保
* @since 2024/3/13
*/
public class EmptyStringAsNullTest {
@Test
void testString() {
String json = "\"\"";
//""
String value = JSON.parseObject(json, String.class);
Assertions.assertEquals("", value);
//null
value = JSON.parseObject(json, String.class, JSONReader.Feature.EmptyStringAsNull);
Assertions.assertNull(value);

//reset json
json = "\" \"";
//" "
value = JSON.parseObject(json, String.class);
Assertions.assertEquals(" ", value);
//" "
value = JSON.parseObject(json, String.class, JSONReader.Feature.EmptyStringAsNull);
Assertions.assertEquals(" ", value);

//""
value = JSON.parseObject(json, String.class, JSONReader.Feature.TrimString);
Assertions.assertEquals("", value);

//null
value = JSON.parseObject(json, String.class, JSONReader.Feature.TrimString, JSONReader.Feature.EmptyStringAsNull);
Assertions.assertNull(value);
}

@Test
void testBean() {
//test String field& map field
String beanJson = "{\"name\":\"\",\"map\":{\"emptyValue\":\"\"}}";
StringBean bean = JSON.parseObject(beanJson, StringBean.class, JSONReader.Feature.EmptyStringAsNull);
Assertions.assertNotNull(bean);
Assertions.assertNull(bean.getName());
Map<String, String> map = bean.getMap();
Assertions.assertFalse(map == null || map.isEmpty());
Assertions.assertNull(map.get("emptyValue"));

//test list field
//todo collection 是否需要过滤空值
beanJson = "{\"list\":[\"emptyValue\",\"\"]}";
bean = JSON.parseObject(beanJson, StringBean.class, JSONReader.Feature.EmptyStringAsNull);
Assertions.assertNotNull(bean);
List<String> list = bean.getList();
Assertions.assertFalse(list == null || list.size() != 2);
Assertions.assertEquals("emptyValue", list.get(0));
Assertions.assertNull(list.get(1));

//test set field
beanJson = "{\"set\":[\"emptyValue\",\"\"]}";
bean = JSON.parseObject(beanJson, StringBean.class, JSONReader.Feature.EmptyStringAsNull);
Assertions.assertNotNull(bean);
Set<String> set = bean.getSet();
Assertions.assertFalse(set == null || set.size() != 1);
Assertions.assertTrue(set.contains("emptyValue"));
System.out.println(1);

beanJson = "{\"treeSet\":[\"emptyValue\",\"\"]}";
bean = JSON.parseObject(beanJson, StringBean.class, JSONReader.Feature.EmptyStringAsNull);

Assertions.assertNotNull(bean);
Set<String> treeSet = bean.getTreeSet();
Assertions.assertFalse(treeSet == null || treeSet.size() != 1);
Assertions.assertTrue(treeSet.contains("emptyValue"));
}

@Test
void testJSONB() {
byte[] jsonbBytes = JSONObject.of("value", " ").toJSONBBytes();
Assertions.assertNull(
JSONB.parseObject(
new ByteArrayInputStream(jsonbBytes),
new JSONReader.Context(JSONReader.Feature.TrimString, JSONReader.Feature.EmptyStringAsNull)
).getString("value")
);

jsonbBytes = JSONObject.of("value", "").toJSONBBytes();
Assertions.assertNull(
JSONB.parseObject(
new ByteArrayInputStream(jsonbBytes),
new JSONReader.Context(JSONReader.Feature.EmptyStringAsNull)
).getString("value")
);
}

@Data
private static class StringBean {
private String name;
private Map<String, String> map;
private List<String> list;
private Set<String> set;
private TreeSet<String> treeSet;
}
}
Loading