[英]How do I override a Java map when converting a JSON to Java Object using GSON?
I have a JSON string which looks like this: 我有一个看起来像这样的JSON字符串:
{
"status": "status",
"date": "01/10/2019",
"alerts": {
"labels": {
"field1": "value1",
"field2": "value2",
"field3": "value3",
"field100": "value100"
},
"otherInfo" : "other stuff"
},
"description": "some description"
}
My corresponding Java classes look like the following: 我对应的Java类如下所示:
public class Status {
private String status;
private String date;
private Alerts alerts;
private String description;
}
And 和
public class Alerts {
private Map<String, String> labels;
private String otherInfo;
public Map<String, String> getLabels() {
return labels();
}
}
I'm parsing the given JSON into Java object using this: 我正在使用以下方法将给定的JSON解析为Java对象:
Status status = gson.fromJson(statusJSONString, Status.class);
This also gives me Alerts object from Status class: 这也为我提供了Status类的Alerts对象:
Alerts alerts = status.getAlerts();
Here is my problem: 这是我的问题:
Let's consider the labels
: 让我们考虑一下
labels
:
I want to make keys in the label
map the case-insensitive. 我想使
label
映射中的键不区分大小写。 So for example, if the provided key/value pair is "field1" : "value1"
, or "Field1" : "value1"
or "fIeLD1":"value1"
, I want to be able to retrieve them by simply calling alerts.getLabels.get("field1")
. 因此,例如,如果提供的键/值对是
"field1" : "value1"
或"Field1" : "value1"
或"fIeLD1":"value1"
,我希望能够通过简单地调用alerts.getLabels.get("field1")
来检索它们alerts.getLabels.get("field1")
。
Ideally, I want to set the keys to be lowercase when the labels
map is originally created. 理想情况下,我想在最初创建
labels
映射时将键设置为小写。 I looked into Gson deserialization examples, but I'm not clear exactly how to approach this. 我研究了Gson反序列化示例,但不清楚如何实现这一目标。
There isnt really much you can do here. 您在这里确实没有什么可以做的。 Even if you extended HashMap, the problem is that when the JSON is de-serialized, it doesn't call native methods.
即使扩展了HashMap,问题仍然在于,当JSON反序列化时,它不会调用本地方法。 What you COULD do is the following, but it is rather cumbersome:
您可以做的是以下操作,但这很麻烦:
import java.util.HashMap;
public class HashMapCaseInsensitive extends HashMap<String, String> {
private boolean convertedToLower = false;
@Override
public String put(String key, String value) {
if(!convertedToLower){
convertToLower();
}
return super.put(key.toLowerCase(), value);
}
@Override
public String get(Object key) {
if(!convertedToLower){
convertToLower();
}
return super.get(key.toString().toLowerCase());
}
private void convertToLower(){
for(String key : this.keySet()){
String data = this.get(key);
this.remove(key);
this.put(key.toLowerCase(), data);
}
convertedToLower = true;
}
}
Though this is not a very generic solution, however, I think this will serve your purpose. 尽管这不是一个非常通用的解决方案,但是我认为这将达到您的目的。
I would like to suggest you create an adapter for Gson which can convert the map values for you. 我建议您为Gson创建一个适配器,该适配器可以为您转换地图值。 The adapter might look like the following.
适配器可能如下所示。
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import java.lang.reflect.Type;
final class GSONAdapter implements JsonDeserializer<String> {
private static final GSONAdapter instance = new GSONAdapter();
static GSONAdapter instance() {
return instance;
}
@Override
public String deserialize(JsonElement jsonElement, Type type,
JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
// Here I am taking the elements which are starting with field
// and then returning the lowercase version
// so that the labels map is created this way
if (jsonElement.getAsString().toLowerCase().startsWith("field"))
return jsonElement.getAsString().toLowerCase();
else return jsonElement.getAsString();
}
}
Now just add the GsonBuilder
to your Gson using the adapter and then try to parse the JSON. 现在,只需使用适配器将
GsonBuilder
添加到您的Gson中,然后尝试解析JSON。 You should get all the values in the lower case as you wanted for the labels
. 您应该根据需要为
labels
获取所有的小写字母值。
Please note that I am just taking the field
variables in my concern and hence this is not a generic solution which will work for every key. 请注意,我只是关心
field
变量,因此这不是适用于每个键的通用解决方案。 However, if your keys have any specific format, this can be easily applied. 但是,如果您的密钥具有任何特定的格式,则可以轻松地应用。
Gson gson = new GsonBuilder()
.registerTypeAdapter(String.class, GSONAdapter.instance())
.create();
Status status = gson.fromJson(statusJSONString, Status.class);
Alerts alerts = status.getAlerts();
Hope that solves your problem. 希望能解决您的问题。
You can write your own MapTypeAdapterFactory
which creates Map
always with lowered keys. 您可以编写自己的
MapTypeAdapterFactory
,它始终使用降低的键来创建Map
。 Our adapter will be based on com.google.gson.internal.bind.MapTypeAdapterFactory
. 我们的适配器将基于
com.google.gson.internal.bind.MapTypeAdapterFactory
。 We can not extend it because it is final
but our Map
is very simple so let's copy only important code: 我们不能扩展它,因为它是
final
但是我们的Map
非常简单,所以我们只复制重要的代码:
class LowercaseMapTypeAdapterFactory implements TypeAdapterFactory {
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
TypeAdapter<String> stringAdapter = gson.getAdapter(TypeToken.get(String.class));
return new TypeAdapter<T>() {
@Override
public void write(JsonWriter out, T value) { }
@Override
public T read(JsonReader in) throws IOException {
JsonToken peek = in.peek();
if (peek == JsonToken.NULL) {
in.nextNull();
return null;
}
Map<String, String> map = new HashMap<>();
in.beginObject();
while (in.hasNext()) {
JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in);
String key = stringAdapter.read(in).toLowerCase();
String value = stringAdapter.read(in);
String replaced = map.put(key, value);
if (replaced != null) {
throw new JsonSyntaxException("duplicate key: " + key);
}
}
in.endObject();
return (T) map;
}
};
}
}
Now, we need to inform that our Map
should be deserialised with our adapter: 现在,我们需要通知我们应该使用适配器反序列化我们的
Map
:
class Alerts {
@JsonAdapter(value = LowercaseMapTypeAdapterFactory.class)
private Map<String, String> labels;
private String otherInfo;
// getters, setters, toString
}
Assume that our JSON
payload
looks like below: 假设我们的
JSON
payload
如下所示:
{
"status": "status",
"date": "01/10/2019",
"alerts": {
"labels": {
"Field1": "value1",
"fIEld2": "value2",
"fielD3": "value3",
"FIELD100": "value100"
},
"otherInfo": "other stuff"
},
"description": "some description"
}
Example usage: 用法示例:
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.internal.JsonReaderInternalAccess;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class GsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
Gson gson = new GsonBuilder().create();
Status status = gson.fromJson(new FileReader(jsonFile), Status.class);
System.out.println(status.getAlerts());
}
}
Above code prints: 上面的代码打印:
Alerts{labels={field1=value1, field100=value100, field3=value3, field2=value2}, otherInfo='other stuff'}
This is really tricky solution and it should be used carefully. 这确实是棘手的解决方案,应谨慎使用。 Do not use this adapter with much complex
Map
-es. 不要将此适配器与非常复杂的
Map
-es一起使用。 From other side, OOP
prefers much simple solutions. 从另一方面来看,
OOP
倾向于更简单的解决方案。 For example, create decorator
for a Map
like below: 例如,为
Map
创建decorator
,如下所示:
class Labels {
private final Map<String, String> map;
public Labels(Map<String, String> map) {
Objects.requireNonNull(map);
this.map = new HashMap<>();
map.forEach((k, v) -> this.map.put(k.toLowerCase(), v));
}
public String getValue(String label) {
return this.map.get(label.toLowerCase());
}
// toString
}
Add new method to Alerts
class: 向
Alerts
类添加新方法:
public Map<String, String> toLabels() {
return new Labels(labels);
}
Example usage: 用法示例:
status.getAlerts().toLabels()
Which gives you a very flexible and secure behaviour. 这为您提供了非常灵活和安全的行为。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.