GSON: Как получить нечувствительный к регистру элемент от Json?

Код, показанный ниже, хорошо работает, когда объект JSON содержит jsonKey, поскольку он был передан методу. Интересно... если есть способ получить значение, присвоенное нечувствительному к регистру представлению ключа?

Пример:

public String getOutputEventDescription(JsonElement outputEvent) throws ParserException {
    return retrieveString(outputEvent, DESCRIPTION);
}

Должно работать независимо от того, определено ли DESCRIPTION как "Описание", "описание" или "DeScRipTIOn"

protected String retrieveString(JsonElement e, String jsonKey) throws ParserException {

JsonElement value = e.getAsJsonObject().get(jsonKey);

if (value == null) {
    throw new ParserException("Key not found: " + jsonKey);
}

if (value.getAsString().trim().isEmpty()) {
    throw new ParserException("Key is empty: " + jsonKey);
}

return e.getAsJsonObject().get(jsonKey).getAsString();
}

Ответ 1

К сожалению, в текущей реализации, похоже, нет способа сделать это. Если вы посмотрите на источник Gson и, более конкретно, на реализацию JsonObject, вы увидите, что базовая структура данных является связанной хэш-картой. Запрос get просто вызывает get на карте, который, в свою очередь, использует хеш-код и равен методу вашего ключа, чтобы найти объект, который вы ищете.

Единственный способ - обеспечить соблюдение некоторых соглашений об именах для ваших ключей. Самый простой способ - заставить все ключи в нижнем регистре. Если вам нужны смешанные кейсы, тогда у вас будет больше трудностей и вам нужно будет написать более сложный алгоритм для преобразования ключей вместо простого вызова jsonKey.toLowerCase().

Ответ 2

К сожалению, регистрация FieldNamingStrategy с помощью GsonBuilder не принесет много пользы, поскольку она переводит только в обратную сторону, чем -заправленное направление: от имени поля Java до имени элемента JSON. Он не может быть разумно использован для ваших целей.

(Подробно:

Результат запроса перевода заканчивается на FieldNamingStrategy.translateName(Field), где переведенное имя используется для получения связанного элемента JSON из JsonObject, который имеет LinkedHashMap<String, JsonElement>, называемый members, отображающий имена элементов JSON для их связанных значений. Переведенное имя используется как параметр для метода get(String) members, а Gson не предоставляет механизма для того, чтобы этот окончательный вызов делался нечувствительным к регистру.

Карта members заполняется вызовами JsonObject.add(String, JsonElement), сделанными из Streams.parseRecursive(JsonReader), с именем элемента JSON, полученным из JsonReader, используемого в качестве ключа для "членов". (JsonReader использует символы точно так же, как они находятся в JSON, с исключением, где найден escape-символ "\".) В течение этого стека вызовов Gson не предоставляет механизма для ключей, используемых для заполнения members, которые необходимо изменить, например, для всех нижних регистров или всего верхнего регистра.

A FieldNamingPolicy работает таким же образом.)

Разумным решением может быть просто использование настраиваемого десериализатора в следующих строках.

input.json:

[
 {"field":"one"},
 {"Field":"two"},
 {"FIELD":"three"},
 {"fIElD":"four"}
]

Foo.java:

import java.io.FileReader;
import java.lang.reflect.Type;
import java.util.Map.Entry;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;

public class Foo
{
  public static void main(String[] args) throws Exception
  {
    GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.registerTypeAdapter(MyClass.class, new MyTypeAdapter());
    Gson gson = gsonBuilder.create();
    MyClass[] myObjects = gson.fromJson(new FileReader("input.json"), MyClass[].class);
    System.out.println(gson.toJson(myObjects));
  }
}

class MyClass
{
  String field;
}

class MyTypeAdapter implements JsonDeserializer<MyClass>
{
  @Override
  public MyClass deserialize(JsonElement json, Type myClassType, JsonDeserializationContext context)
      throws JsonParseException
  {
    // json = {"field":"one"}
    JsonObject originalJsonObject = json.getAsJsonObject();
    JsonObject replacementJsonObject = new JsonObject();
    for (Entry<String, JsonElement> elementEntry : originalJsonObject.entrySet())
    {
      String key = elementEntry.getKey();
      JsonElement value = originalJsonObject.get(key);
      key = key.toLowerCase();
      replacementJsonObject.add(key, value);
    }
    return new Gson().fromJson(replacementJsonObject, MyClass.class);
  }
}

В качестве альтернативы вы можете сначала обработать необработанный JSON, чтобы изменить все имена элементов в одном и том же случае, все нижние или все верхние. Затем передайте измененный JSON в Gson для десериализации. Это, конечно, замедлит обработку JSON.

Если вы можете изменить код Gson для своего проекта, то, вероятно, часть для изменения для наиболее эффективного результата - это вызов name = nextString((char) quote); в JsonReader. Поскольку nextString(char) также используется для получения значения элемента JSON, я, вероятно, просто сделаю его копию для получения имени, а затем сделаю небольшие изменения, чтобы заставить имена элементов для всех нижних или всех верхних регистров. Конечно, этот подход затем блокирует ваш проект до одного выпуска Gson, иначе вам нужно будет повторить это изменение, чтобы перейти на новую версию Gson.

С Jackson ситуация выглядит, к сожалению, аналогичной. Переводы с PropertyNamingStrategy работают, к сожалению, почти так же: они переходят от имени поля Java к имени элемента JSON. Ни одна из доступных изменений JsonParser.Feature не настроила бы JsonParser, чтобы заставить имена элементов JSON для всех верхних или всех нижних регистров.

Ответ 3

Я столкнулся с подобной проблемой. Я сделал это, чтобы обойти эту проблему. (Заменили все ключи соответствующей версией нижнего регистра и имели все поля нижнего регистра в соответствующем классе). Надеюсь, это поможет.

 input = input.replaceAll("\\s","");
        Matcher m = Pattern.compile("\"\\b\\w{1,}\\b\"\\s*:").matcher(input);
        StringBuilder sanitizedJSON = new StringBuilder();
        int last = 0;
        while (m.find()) {
            sanitizedJSON.append(input.substring(last, m.start()));
            sanitizedJSON.append(m.group(0).toLowerCase());
            last = m.end();
        }
        sanitizedJSON.append(input.substring(last));

        input = sanitizedJSON.toString();