简体   繁体   English

使用GSON将JSON转换为Java对象时,如何覆盖Java映射?

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM