GSON: Как получить нечувствительный к регистру элемент от Json?
Код, показанный ниже, хорошо работает, когда JSON
объект содержит jsonKey
как это было передано в метод. Интересно... есть ли способ получить значение, назначенное нечувствительному к регистру представлению ключа?
Пример:
public String getOutputEventDescription(JsonElement outputEvent) throws ParserException {
return retrieveString(outputEvent, DESCRIPTION);
}
Должен работать независимо от того, определено ли DESCRIPTION как "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();
}
4 ответа
К сожалению, в текущей реализации, похоже, нет способа сделать это. Если вы посмотрите на источник Gson, а точнее на реализацию JsonObject, то увидите, что основная структура данных представляет собой связанную хэш-карту. Вызов get просто вызывает метод get on map, который, в свою очередь, использует хеш-код и метод equals вашего ключа для поиска искомого объекта.
Единственным выходом из ситуации является применение некоторых соглашений об именах для ваших ключей. Самый простой способ состоит в том, чтобы заставить все ключи в нижнем регистре. Если вам нужны ключи со смешанным регистром, у вас будет больше трудностей, и вам нужно будет написать более сложный алгоритм преобразования ключей вместо простого вызова jsonKey.toLowerCase().
К сожалению, регистрация 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
быть измененным, например, быть сделанным полностью строчными или заглавными буквами.
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.
С Джексоном ситуация выглядит, к сожалению, похожей. Переводы с PropertyNamingStrategy
работают, к сожалению, почти так же: они переводят имя поля Java в имя элемента JSON. Ни один из доступных JsonParser.Feature
изменения будут настраивать JsonParser
заставить имена элементов JSON использовать все прописные или строчные буквы.
Я сталкивался с подобной проблемой. Я сделал это, чтобы обойти проблему. (Заменены все ключи на соответствующие им строчные буквы и все строчные поля в соответствующем классе). Надеюсь это поможет.
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();
Я наткнулся на этот вопрос, когда столкнулся с проблемой, когда на двух конечных точках использовалось другое соглашение об именах, и впоследствии обнаружил менее инвазивное решение.
Gson поддерживает настройку соглашения об именах, которое используется при сопоставлении имен моделей Java с именами JSON как при сериализации, так и при десериализации. Используйте метод setFieldNamingPolicy построителя, чтобы изменить это поведение.
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE);
Gson gson = gsonBuilder.create();
См. здесь хорошую статью по этому вопросу, включая обзор различных политик.
На самом деле это решение не чувствительно к регистру, но оно позволяет обойти многие ситуации, когда регистр не совпадает.