Как переопределить карту Java при преобразовании JSON в объект Java с помощью GSON? - PullRequest
0 голосов
/ 27 февраля 2019

У меня есть строка JSON, которая выглядит следующим образом:

{
 "status": "status",
 "date": "01/10/2019",
 "alerts": {
     "labels": {
         "field1": "value1",
         "field2": "value2",
         "field3": "value3",
         "field100": "value100"
     },
     "otherInfo" : "other stuff"
 },
 "description": "some description"
}

Мои соответствующие классы Java выглядят следующим образом:

public class Status {
    private String status;
    private String date;
    private Alerts alerts;
    private String description;
}

И

public class Alerts {
    private Map<String, String> labels;
    private String otherInfo;

    public Map<String, String> getLabels() {
        return labels();
    }
}

Я анализирую данный JSON в объект Java, используя это:

Status status = gson.fromJson(statusJSONString, Status.class);

Это также дает мне объект оповещений из класса Status:

Alerts alerts = status.getAlerts();

Вот моя проблема:

Давайте рассмотрим labels:

Я хочу, чтобы ключи в label отображались без учета регистра.Так, например, если предоставленная пара ключ / значение равна "field1" : "value1", или "Field1" : "value1", или "fIeLD1":"value1", я хочу иметь возможность получить их, просто вызвав alerts.getLabels.get("field1").

В идеале, я хочуустановить ключи в нижнем регистре при первоначальном создании карты labels.Я посмотрел примеры десериализации Gson, но не совсем точно, как к этому подойти.

Ответы [ 3 ]

0 голосов
/ 27 февраля 2019

Хотя это не очень общее решение, однако, я думаю, что оно послужит вашей цели.

Я хотел бы предложить вам создать адаптер для Gson, который может преобразовывать значения карт для вас.Адаптер может выглядеть следующим образом.

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();
    }
}

Теперь просто добавьте GsonBuilder к вашему Gson с помощью адаптера и попробуйте проанализировать JSON.Вы должны получить все значения в нижнем регистре, как вы хотели для labels.

Обратите внимание, что я просто использую переменные field в своей области, и, следовательно, это не общее решение, которое будет работать для каждого ключа.Однако, если ваши ключи имеют какой-либо определенный формат, это может быть легко применено.

Gson gson = new GsonBuilder()
        .registerTypeAdapter(String.class, GSONAdapter.instance())
        .create();

Status status = gson.fromJson(statusJSONString, Status.class);
Alerts alerts = status.getAlerts();

Надеюсь, что решит вашу проблему.

0 голосов
/ 28 февраля 2019

Вы можете написать свой собственный MapTypeAdapterFactory, который создает Map всегда с опущенными клавишами.Наш адаптер будет основан на com.google.gson.internal.bind.MapTypeAdapterFactory.Мы не можем расширить его, потому что это 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;
            }
        };
    }
}

Теперь нам нужно сообщить, что наш Map должен быть десериализован с помощьюнаш адаптер:

class Alerts {

    @JsonAdapter(value = LowercaseMapTypeAdapterFactory.class)
    private Map<String, String> labels;

    private String otherInfo;

    // getters, setters, toString
}

Предположим, что наш JSON payload выглядит следующим образом:

{
  "status": "status",
  "date": "01/10/2019",
  "alerts": {
    "labels": {
      "Field1": "value1",
      "fIEld2": "value2",
      "fielD3": "value3",
      "FIELD100": "value100"
    },
    "otherInfo": "other stuff"
  },
  "description": "some description"
}

Пример использования:

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());
    }
}

Над кодом печатается:

Alerts{labels={field1=value1, field100=value100, field3=value3, field2=value2}, otherInfo='other stuff'}

Это действительно хитрое решение, и его следует использовать осторожно.Не используйте этот адаптер с очень сложными Map -ами.С другой стороны, OOP предпочитает гораздо более простые решения.Например, создайте decorator для Map, как показано ниже:

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
}

Добавить новый метод в Alerts class:

public Map<String, String> toLabels() {
    return new Labels(labels);
}

Пример использования:

status.getAlerts().toLabels()

Что дает вам очень гибкое и безопасное поведение.

0 голосов
/ 27 февраля 2019

На самом деле здесь мало что можно сделать.Даже если вы расширили HashMap, проблема в том, что при десериализации JSON он не вызывает нативные методы.Что вы МОЖЕТЕ сделать, это следующее, но это довольно громоздко:

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;
    }

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...