英文:
How can I make Gson.fromJson() return an empty list instead of null for a field with a null value?
问题
I am using Gson to deserialize a bunch of objects, some of which are more complex and some "lightweight".
I am in a bit of a conundrum over here, as I have been expecting another behavior from GSON.fromJson() when reading a field with null
value. More precisely, looking at this example
public class TestApplication {
@Data
@NoArgsConstructor
public class User {
private String userName;
@SerializedName(value = "eMailAddress")
private String email;
private final List<Object> list = new ArrayList<>();
}
public static void main(String[] args) {
String x = "{\"userName\": \"test\", \"eMailAddress\": \"dan@gmail.com\", \"list\": null}";
User from = new Gson().fromJson(x, User.class);
System.out.println(from);
}
}
This prints:
TestApplication.User(userName=test, email=dan@gmail.com, list=null)
I was expecting the output list to be empty (list=[]
) and not null.
Can I do anything to achieve the behavior I need? My application uses lists of objects and it is not feasible to register a typeAdapter for each of them.
How would I go about writing a custom deserializer? This next example would be simple enough if it were to work:
class CollectionDeserializer implements JsonDeserializer<Collection<?>> {
public Collection<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return ObjectUtils.isEmpty(json) ? new ArrayList<>() : new Gson().fromJson(json, Collection.class);
}
}
What I have tried:
- Implementing a custom deserializer (above) with no success.
- @JsonSetter(nulls= Nulls.SKIP) annotation with no effect on the Gson parsing.
英文:
I am using Gson to deserialize a bunch of objects, some of which are more complex and some "lightweight".
I am in a bit of a cunundrum over here, as I have been expecting another behaviour from GSON.fromJson() when reading a field with null
value. More precisely, looking at this example
public class TestApplication{
@Data
@NoArgsConstructor
public class User{
private String userName;
@SerializedName(value = "eMailAddress")
private String email;
private final List<Object> list = new ArrayList<>();
}
public static void main(String[] args) {
String x = "{\n" + "\"userName\": \"test\""
+ ",\n" + "\"eMailAddress\": \"dan@gmail.com\""
+ ",\n" + "\"list\": null\n"
+ "}";
User from = new Gson().fromJson(x, User.class);
System.out.println(from);
}
}
This prints:
TestApplication.User(userName=test, email=dan@gmail.com, list=null)
I was expecting the output list to be empty (list=[]
) and not null.
Can I do anything to achieve the behaviour I need? My application uses lists of objects and it is not feasable to register a typeAdapter for each of them.
How would I go about writing a custom deserializer? This next example would be simple enough if it were to work:
class CollectionDeserializer implements JsonDeserializer<Collection<?>> {
public Collection<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return ObjectUtils.isEmpty(json) ? new ArrayList<>() : new Gson().fromJson(json, Collection.class);
}
}
What I have tried:
- implementing a customer deserializer ( above ) with no success.
- @JsonSetter(nulls= Nulls.SKIP) annotation with no effect on the Gson parsing.
答案1
得分: 2
Your JSON string is explicitly saying that this list is null, so I'm not sure why you expected it to be empty in the first place. For an empty string, the JSON string should be []
.
Nevertheless, if you still want to convert null lists to empty lists in your custom deserializer, the answer is simple. Your custom deserializer is not working because it's testing the JSON element being null or empty. It's neither of those; it's a value that represents a null element. Use JsonElement.isJsonNull:
class CollectionDeserializer implements JsonDeserializer<Collection<?>> {
public Collection<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return json.isJsonNull() ? Collections.emptyList() : ...;
}
}
英文:
Your JSON string is explicitly saying that this list is null, so I'm not sure why you expected it to be empty in the first place. For an empty string, the JSON string should be []
.
Nevertheless, if you still want to convert null lists to empty lists in your custom deserializer, the answer is simple. Your custom deserializer is not working because it's testing the the json element being null or empty. It's neither of those: it's a value that represents a null element. Use JsonElement.isJsonNull:
class CollectionDeserializer implements JsonDeserializer<Collection<?>> {
public Collection<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return json.isJsonNull() ? Collections.emptyList() : ...;
}
}
答案2
得分: 2
以下是您要翻译的内容:
这可以使用Jackson完成,而无需显式将字段设置为null:
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
public class TestApplication{
@Data
@NoArgsConstructor
public static class User{
private String userName;
@JsonProperty("eMailAddress")
private String email;
private final List<Object> list = new ArrayList<>();
}
public static void main(String[] args) throws JsonProcessingException {
String x = "{\"userName\": \"test\", \"eMailAddress\": \"dan@gmail.com\"}";
ObjectMapper mapper = new ObjectMapper();
User from = mapper.readValue(x, User.class);
System.out.println(from);
}
}
这将打印:
TestApplication.User(userName=test, email=dan@gmail.com, list=[])
英文:
It can be done with Jackson, and without explicitly set the field to null:
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
public class TestApplication{
@Data
@NoArgsConstructor
public static class User{
private String userName;
@JsonProperty("eMailAddress")
private String email;
private final List<Object> list = new ArrayList<>();
}
public static void main(String[] args) throws JsonProcessingException {
String x = "{\n" + "\"userName\": \"test\""
+ ",\n" + "\"eMailAddress\": \"dan@gmail.com\""
+ "}";
ObjectMapper mapper = new ObjectMapper();
User from = mapper.readValue(x, User.class);
System.out.println(from);
}
}
This prints:
TestApplication.User(userName=test, email=dan@gmail.com, list=[])
答案3
得分: 1
以下是您要求的中文翻译部分:
这个问题可以通过自定义TypeAdapterFactory
来解决:
class NullListToEmptyFactory implements TypeAdapterFactory {
public static final NullListToEmptyFactory INSTANCE = new NullListToEmptyFactory();
private NullListToEmptyFactory() {
}
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
Class<?> rawType = type.getRawType();
// 仅处理List和ArrayList;让其他工厂处理不同类型
if (rawType != List.class && rawType != ArrayList.class) {
return null;
}
// 委托处理非空值的反序列化和序列化
TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
return new TypeAdapter<T>() {
@Override
public void write(JsonWriter out, T value) throws IOException {
delegate.write(out, value);
}
@Override
public T read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
// 由于在`create`方法开始处进行了检查,所以是安全的
@SuppressWarnings("unchecked")
T t = (T) new ArrayList<>();
return t;
} else {
return delegate.read(in);
}
}
};
}
}
然后,您可以像这样在GsonBuilder
上注册此工厂:
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(NullListToEmptyFactory.INSTANCE)
.create();
使用TypeAdapterFactory
有以下优势:
- 它使用
TypeAdapter
,通常比JsonDeserializer
性能更好,因为它直接在JSON流上操作,无需首先将其转换为JsonElement
- 它允许委托给默认适配器:
- 无需创建第二个
Gson
实例;反序列化将使用您现有的Gson
实例以及您进行的所有配置 - 它支持自定义元素类型,例如
List<MyClass>
,因为委托执行所有类型解析工作
- 无需创建第二个
重要提示: 无论您选择哪种解决方案,都无法处理JSON数据中字段丢失的情况。目前,Gson在这方面的支持不太好,可以查看此功能请求。如果JSON数据中缺少字段,您目前必须明确使用非null的List
初始化字段(并确保您的类具有无参数构造函数;更多信息),否则如果在JSON数据中缺少该字段,该字段将为null
。
英文:
This can be solved with a custom TypeAdapterFactory
:
class NullListToEmptyFactory implements TypeAdapterFactory {
public static final NullListToEmptyFactory INSTANCE = new NullListToEmptyFactory();
private NullListToEmptyFactory() {
}
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
Class<?> rawType = type.getRawType();
// Only handle List and ArrayList; let other factories handle different types
if (rawType != List.class && rawType != ArrayList.class) {
return null;
}
// Delegate which handles deserialization of non-null values, and serialization
TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
return new TypeAdapter<T>() {
@Override
public void write(JsonWriter out, T value) throws IOException {
delegate.write(out, value);
}
@Override
public T read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
// Safe due to check at beginning of `create` method
@SuppressWarnings("unchecked")
T t = (T) new ArrayList<>();
return t;
} else {
return delegate.read(in);
}
}
};
}
}
You can then register this factory on a GsonBuilder
like this:
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(NullListToEmptyFactory.INSTANCE)
.create();
The usage of TypeAdapterFactory
has the following advantages:
- It uses
TypeAdapter
which normally performs better thanJsonDeserializer
because it directly operates on the JSON stream and does not have to convert it to aJsonElement
first - It allows delegating to the default adapter:
- There is no need to create a second
Gson
instance; deserialization will use your existingGson
instance and all of its configuration you made - It supports custom element types, for example
List<MyClass>
because the delegate does all the type resolution work
- There is no need to create a second
Important: With any solution you choose for this problem, none of them will handle the case where the field is missing in the JSON data. Gson does not support that very well at the moment, see this feature request. You currently have to explicitly initialize the field with a non-null
List
(and make sure your class has a no-args constructor; more information), otherwise the field will be null
if it is missing in the JSON data.
答案4
得分: 0
可以使用 Gson,并显式将字段设置为空列表来完成:
替换:
+ ",\n" + "\"list\": null\n"
为:
+ ",\n" + "\"list\": []\n"
这会打印:
TestApplication.User(userName=test, email=null, list=[])
英文:
It can be done with Gson with explicitly set the field to empty list:
Replace:
+ ",\n" + "\"list\": null\n"
With:
+ ",\n" + "\"list\": []\n"
This prints:
TestApplication.User(userName=test, email=null, list=[])
答案5
得分: 0
一个空列表不同于一个空的列表。
如果你想要所有的空数组变成空数组,那么你只能使用自定义适配器。这需要很多工作。你需要以下内容:
ArrayTypeAdapter的新变体,CollectionTypeAdapterFactory和其他设置类。
将ArrayTypeAdapter中的以下行更改为:
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return Array.newInstance(componentType, 0);
}
而不是这样做,我建议你添加一个输入验证器 @NotNull
或在反序列化后程序atically 将 null
更改为 []
对于特定的值。
英文:
A null list is not the same as an empty list.
You can only use a custom adapter if you want all null arrays to become empty arrays. It's a lot of work. You need the following:
New variant of ArrayTypeAdapter, CollectionTypeAdapterFactory and other setup classes.
Change the following lines of ArrayTypeAdapter
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
to
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return Array.newInstance(componentType, 0);
}
Instead of doing this, I recommend that you add an input validator @NotNull
or programmatically change the null
to []
for specific values after deserialisation
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论