В эти выходные была одна и та же публичная статья нескольких технических блоггеров.В fastjson была обнаружена еще одна уязвимость, и этот вред может привести к параличу сервиса!После считывания экрана, вскоре после предыдущего инцидента с уязвимостью, в fastjson возникла серьезная ошибка. В настоящее время многие проекты используют fastjson для преобразования объектов и данных JSON, и их необходимо обновлять и повторно развертывать, что, можно сказать, требует много времени и труда. В то же время это принесло мне и новое мышление.Ввиду большого количества мощных библиотек с открытым исходным кодом мы не можем слепо внедрить их в проект.Достаточно определенного фактора нестабильности во многих фреймворках с открытым исходным кодом, чтобы проект пострадал стихийное бедствие. Воспользовавшись выходными, я дома изучу Gson, отличный фреймворк с открытым исходным кодом, который также имеет функции JSON и преобразования объектов.Я планирую постепенно заменить использование fastjson в будущих проектах на Gson и записывать содержание обучения и резюме, надеясь помочь своим друзьям.
Все фрагменты кода, задействованные в этой статье, находятся на следующем складе. Заинтересованные партнеры могут ознакомиться с ними и изучить их:
Прежде чем официально представить Gson, мы можем начать софициальная викиВзгляните на описание Гсона, чтобы понять, что это такое?
Gson — это библиотека Java, которую можно использовать для преобразования объектов Java в их представление JSON, а также для преобразования строки JSON в эквивалентный объект Java.
Как видно из описания, Gson — это библиотека Java для преобразования объектов Java в строковые данные в формате JSON и обратно. Первоначально он широко использовался в Google на платформе Android и на сервере Java. После того, как он был открыт в 2008 году, он стал еще одной широко используемой платформой с открытым исходным кодом Google.На сегодняшний день *(2019.09.08)* имеет более 1W6 звезд на GitHub.Библиотека классов с той же функцией Джексона интегрирована в Spring. Framework, а также fastjson с открытым исходным кодом от Alibaba и т. д.
Что касается функций, Gson предоставляет простой API.fromJson/toJsonЧтобы реализовать преобразование между Java и JSON и создать компактный и удобочитаемый строковый вывод JSON, он также поддерживает сложное преобразование объектов и богатое пользовательское представление, достаточное для удовлетворения большинства наших потребностей в обработке данных JSON в повседневной разработке.
Обычно мы называем преобразование между объектами и строками JSON сериализацией и десериализацией (Serialization/Deserialization). Процесс преобразования объекта в строку JSON называется сериализацией, а процесс преобразования строки JSON в объект называется десериализацией.
Основное использование Gson
Операции сериализации и десериализации с использованием фреймворка Gson неразделимы.com.google.gson.GsonObject, который также является ключевым объектом платформы Gson, предоставляет общедоступный API с несколькими методами сериализации и десериализации.
Существует два основных способа создания объектов Gson:
использоватьnewКлючевое слово создается напрямую:Gson gson = new Gson()
Создан из объектов GsonBuilder:Gson gson = new GsonBuilder().create()
Обычно объекты Gson, созданные двумя вышеуказанными методами, имеют одинаковое поведение при сериализации и десериализации, но при построении объекта Gson вторым методом допускается дополнительная настройка поведения, например форматирование строк JSON.nullзначение и так далее.
Java-сериализация
Сериализация простых объектов
Мы можем использовать следующие примеры, чтобы увидеть различные эффекты сериализации объектов Java двумя указанными выше способами:
public class ResultTest {
@Test
void test_serialization() {
Gson gson = new Gson();
Result result = new Result(200, "成功", null);
String json = gson.toJson(result);
System.out.println("json is " + json);
Gson buildedGson = new GsonBuilder().setPrettyPrinting().serializeNulls().create();
String buildedJson = buildedGson.toJson(result);
System.out.println("buildedJson is " + buildedJson);
}
class Result {
private int code;
private String message;
private Object data;
public Result(int code, String message, Object data) {
this.code = code;
this.message = message;
this.data = data;
}
}
}
Запустив тестовый пример, вы увидите следующий вывод журнала в консоли:
Как видно из результатов, поведение объекта Gson по умолчанию сериализует объект.nullполя значений игнорируются, аcom.google.gson.GsonBuilder#serializeNullsметод позволит сериализовать объекты Gsonnullполе; а обычная сериализованная строка JSON имеет компактный формат, что позволяет экономить память строк, используяcom.google.gson.GsonBuilder#setPrettyPrintingОкончательная выходная строка JSON после метода имеет более читаемый формат. Конечно, в дополнение к этим двум методам GsonBuilder также предоставляет множество API-интерфейсов для настройки поведения сериализации и десериализации, которые мы объясним далее в следующих разделах.
JosnObject генерирует JSON
В дополнение к вышеперечисленным методам преобразования объектов пользовательских классов в JSON вы также можете использовать JsonObject, предоставляемый инфраструктурой Gson, для построения обычных объектов, а затем использоватьtoJsonМетод генерирует строку JSON, дополняет приведенный ниже тестовый класс в исходном тестовом классе и запускает его, чтобы увидеть результат следующим образом.
Использование JsonObjectaddProperty(property,value)Этот метод можно использовать только для добавления четырех типов данных: строки, числа, логического значения и символа, поскольку внутренний вызовcom.google.gson.JsonObject#add, инкапсулируйте значение в объект JsonPrimitive, а затем сохраните его во внутреннем пользовательскомLinkedTreeMapВ членах переменных коллекции; если вам нужно добавить другие объекты к объекту JsonObject, вам нужно использовать его напрямуюadd(String property, JsonElement value)метод для добавления объекта JsonElement. JsonElement здесь является абстрактным классом.И JsonObject, и JsonPrimitive наследуют JsonElement, поэтому мы, наконец, используем другой объект JsonObject в качестве объекта свойства исходного JsonObject:
Gson gson = new Gson();
JsonObject jsonObject = new JsonObject();
//...
JsonObject nestJsonObject = new JsonObject();
nestJsonObject.addProperty("username", "one");
nestJsonObject.addProperty("score", 99);
jsonObject.add("data", nestJsonObject);
String toJson2 = gson.toJson(jsonObject);
System.out.println(toJson2);
// {"code":400,"message":"参数错误","data":{"username":"one","score":99}}
десериализация JSON
Десериализация простых объектов
Теперь давайте посмотрим на использование десериализации JSON в объекты Java.com.google.gson.Gson#fromJson, его основное использованиеfromJson(String json, Class<T> classOfT), попробуйте преобразовать строку JSON в объект указанного класса, если преобразование не удастся, он выдастJsonSyntaxExceptionаномальный. Мы можем добавить тестовый пример к исходному коду и запустить его, чтобы увидеть эффект:
@Test
void test_deserialization() {
String json = "{\"code\":400,\"message\":\"参数错误\"}";
Result result = new Gson().fromJson(json, Result.class);
Assertions.assertEquals(400, result.code); // true
Assertions.assertEquals("参数错误", result.message); // true
}
Десериализовать карту
Помимо сериализации строк JSON в пользовательские объекты Java, мы также можем конвертировать в коллекции Map.Gson обеспечивает преобразование в коллекции Map, которые очень просты в использовании:
Следует отметить, что реальный тип преобразованного объекта Map — это не HashMap, который мы часто используем, а пользовательская коллекция Gson.LinkedTreeMap, который реализует интерфейс Map для хранения пар "ключ-значение", оптимизирует реализацию добавления и удаления и рассматривает порядок хранения пар "ключ-значение" как порядок обхода, то есть сначала просматривается первое сохраненное. Кроме того, числовые данные в строке JSON будут преобразованы в тип Double, иtrue/falseДанные будут преобразованы в логический тип, конкретная основа суждения может относиться кcom.google.gson.internal.bind.ObjectTypeAdapter#readреализация метода.
JSON и массив, преобразование списка
Преобразование JSON в массив
Когда мы выполняем преобразование массива данных JSON, оно похоже на обычное преобразование объектов.toJsonМетод напрямую используется для преобразования данных JSON,fromJsonПреобразует указанный тип массива в массив соответствующего типа.
Чтобы преобразовать данные списка в данные JSON, способ использования Gson такой же, как и для данных массива; главное здесь заключается в том, что операция преобразования данных JSON в объект списка немного отличается, чтобы преобразовать данные массива JSON в пользовательский class List, мы следуем оригинальному написанию следующим образом:
Но, к сожалению, после запуска этого кода он выдаетClassCastExceptionИсключение описывается следующим образом:
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to com.one.learn.Person
...
Из приведенного выше описания мы можем знать, что выполнениеfromJsonПосле этого тип элемента List, полученный после десериализации, будет LinkedTreeMap, а не Person, поэтому при доступе к свойству id как к объекту Person он выдастClassCastExceptionаномальный. Итак, как с этим бороться, нам нужно позвонить другому GsonfromJsonметод:fromJson(String json, Type typeOfT), сначала посмотрите, как использовать
@Test
public void givenJsonString_whenCorrectDeserializing_() {
Gson gson = new Gson();
String inputString = "[{\"id\":1,\"name\":\"one\"},{\"id\":2,\"name\":\"two\"}]";
Type type = new TypeToken<List<Person>>(){}.getType();
List<Person> outputList = gson.fromJson(inputString, type);
int id = outputList.get(0).getId();
assertEquals(1, id); // true
assertEquals("one", outputList.get(0).getName()); // true
}
Объекту Type в этом методе передается объект TypeToken.getTypeПолученный метод является универсальным типом, связанным с объектом TypeToken. Здесь TypeToken — это класс, введенный Gson для поддержки дженериков для решения проблемы, связанной с тем, что Java не может обеспечить представление универсального типа, поскольку конструктор TypeTokenprotectedМодифицированный, не может быть построен напрямую, его нужно записать какnew TypeToken<List<String>>() {}.getType()форма.
Расширенное использование Gson
Коснувшись основного использования Gson, мы продолжим изучать другие способы использования Gson.
Десериализация общих объектов
Содержание предыдущего раздела кратко коснулось поддержки Gson дженериков. Далее мы будем использовать код, чтобы продемонстрировать его мощь. Во-первых, мы настроим приведенный выше класс Result, чтобы он принимал универсальные параметры:
class Result<T> {
private int code;
private String message;
private T data;
public Result(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
}
Затем разберите строку JSON со встроенными объектами в объект Result Пример кода выглядит следующим образом:
@Test
void test_genric_object() {
String json = "{\"code\":200,\"message\":\"操作成功\",\"data\":{\"username\": \"one\",\"avater\": \"image.jpg\"" +
"}}";
Type type = new TypeToken<Result<User>>(){}.getType();
Result<User> result = new Gson().fromJson(json, type);
Assertions.assertEquals(200, result.code);
Assertions.assertEquals("one", result.data.getUsername());
Assertions.assertEquals("image.jpg", result.data.getAvater());
}
class User {
private String username;
private String avater;
public String getUsername() {
return username;
}
public String getAvater() {
return avater;
}
}
Используйте объект TypeToken для получения определенного универсального типа Result , а затем вfromJsonПереданный метод выполнит операцию десериализации в соответствии с соответствующим типом.
пользовательская сериализация
Если мы хотим выполнить специальную обработку некоторых полей объектов Java, например сериализацию скрытых полей, форматирование данных полей и т. д., мы можем настроить логику сериализации, реализовав интерфейс JsonSerializer. Например, если нам нужно обработать определенный формат для свойств типа Date, мы можем объявить класс DateSerializer для реализации следующего:
class DateSerializer implements JsonSerializer<Date> {
SimpleDateFormat dateTime = new SimpleDateFormat("yyyy-MM-dd");
@Override
public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(dateTime.format(src));
}
}
Затем перед созданием объекта Gson используйте GsonBuilder для регистрации экземпляра DateSerializer следующим образом:
Gson gson = new GsonBuilder().registerTypeAdapter(Date.class, new DateSerializer()).create();
Таким образом, как только вы столкнетесь с полем типа Дата, которое нужно сериализовать, оно передаст пользовательскийserializeметод преобразует дату вyyyy-MM-ddформат для вывода, как показано в следующем примере кода:
@Test
void test_dateSerializer() {
MyObject myObject = new MyObject(new Date(), "one");
Gson gson = new GsonBuilder().registerTypeAdapter(Date.class, new DateSerializer()).create();
String json = gson.toJson(myObject);
String exceptedJson = "{\"date\":\"2019-09-08\",\"name\":\"one\"}";
Assertions.assertEquals(exceptedJson, json); // true
}
class MyObject {
private Date date;
private String name;
public MyObject(Date date, String name) {
this.date = date;
this.name = name;
}
public MyObject() {
}
}
пользовательская десериализация
Аналогично пользовательской реализации сериализации, если вы хотите настроить логику десериализации, вам также необходимо реализовать интерфейс с именем JsonDeserializer для реализации пользовательской логики десериализации. Например, теперь есть содержимое строки JSON как{"CODE": 400, "MESSAGE": "参数错误"}, необходимо десериализовать в упомянутый выше объект Result.Поскольку имена полей разные, для достижения соответствующего преобразования необходимо настроить класс ResultDeserializer, который реализован следующим образом:
class ResultDeserializer implements JsonDeserializer<Result> {
@Override
public Result deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject object = json.getAsJsonObject();
Result<Object> result = new Result<>(object.getAsJsonPrimitive("CODE").getAsInt(),object.getAsJsonPrimitive("MESSAGE").getAsString(), null);
return result;
}
}
Следующим шагом является использование GsonBuilder для регистрации экземпляра ResultDeserializer, создания соответствующего объекта Gson и выполнения операции десериализации:
@Test
void test_resultDeserializer() {
//language=JSON
String json = "{\"CODE\": 400,\"MESSAGE\": \"参数错误\"}";
Gson gson = new GsonBuilder().registerTypeAdapter(Result.class, new ResultDeserializer())
.create();
Result result = gson.fromJson(json, Result.class);
Assertions.assertEquals(400, result.code); // true
Assertions.assertEquals("参数错误", result.message); // true
}
Общие аннотации Gson
Помимо предоставления некоторых API-интерфейсов для разработчиков, Gson также имеет некоторые аннотации с функциями, которые можно использовать.Далее мы представим наиболее часто используемые аннотации в Gson.
@Expose
Эту аннотацию можно использовать только для полей. Функция указывает, будет ли соответствующее поле отображаться во время сериализации или десериализации. Существует два атрибута.serializeиdeserialize, оба верны по умолчанию. При аннотации поля@Expose(serialize = true, deserialize = false), это означает, что при сериализации поле видно, а при десериализации присваивание игнорируется. Дополнительным моментом, который следует отметить, является то, что аннотация @Expose ограничена только тогда, когда Gson построен способом GsonBuilder, и должна быть вызвана перед сборкой.excludeFieldsWithoutExposeAnnotationметод, ниже приведен конкретный пример использования:
@Test
void test_expose() {
MySubClass subclass = new MySubClass(42L, "the answer", "Verbose field not to serialize");
MyClass source = new MyClass(1L, "foo", "bar", subclass);
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
String s = gson.toJson(source);
System.out.println(s);
// {"id":1,"name":"foo","subclass":{"id":42,"description":"the answer","otherVerboseInfo":"Verbose field not to serialize"}}
}
@Data
@AllArgsConstructor
class MyClass {
private long id;
@Expose(serialize = false, deserialize = true)
private String name;
private transient String other;
@Expose
private MySubClass subclass;
}
@Data
@AllArgsConstructor
class MySubClass {
@Expose
private long id;
@Expose
private String description;
@Expose
private String otherVerboseInfo;
}
в ГсонеtransientПоля с измененными ключевыми словами не сериализуются и не десериализуются по умолчанию, что соответствует собственным операциям сериализации и десериализации Java.
@Since
Эта аннотация используется для обозначения версии соответствующего поля или типа, чтобы Gson мог указать номер версии для операций сериализации и десериализации. Эта аннотация полезна, когда в классе есть несколько версий полей, соответствующих данным JSON в веб-службе.
Опять же, эта аннотация предназначена только для использованияGsonBuilderОбъект Gson, созданный с использованиемsetVersionМетод действителен, когда указан номер версии, и анализируются только поля соответствующей версии в объекте.Ниже приведен конкретный пример:
public class VersioningSupportTest {
@Test
void test() {
VersionedClass versionedObject = new VersionedClass();
Gson gson = new GsonBuilder().setVersion(1.0).create();
String jsonOutput = gson.toJson(versionedObject);
System.out.println(jsonOutput); // {"newField":"new","field":"old"}
}
}
class VersionedClass {
@Since(1.1)
private final String newerField;
@Since(1.0)
private final String newField;
private final String field;
public VersionedClass() {
this.newerField = "newer";
this.newField = "new";
this.field = "old";
}
}
@SerializedName
Эта аннотация относительно проста в использовании, но также очень полезна. @SerializedName указывает имя, используемое при сериализации и десериализации поля-члена. Ниже приведено конкретное использование:
public class JSONFieldNamingSupportTest {
private class SomeObject {
@SerializedName("custom_naming")
private final String someField;
private final String someOtherField;
public SomeObject(String a, String b) {
this.someField = a;
this.someOtherField = b;
}
}
@Test
void test() {
SomeObject someObject = new SomeObject("first", "second");
String jsonRepresentation = gson.toJson(someObject);
System.out.println(jsonRepresentation);
// {"custom_naming":"first","someOtherField":"second"}
SomeObject someObject1 = gson.fromJson(jsonRepresentation, SomeObject.class);
System.out.println(someObject1);
// SomeObject{someField='first', someOtherField='second'}
}
}
@JsonAdapter
@JsonAdapterОсновная функция — заменаGsonBuilder.registerTypeAdapterВыполнение метода, непосредственно через@JsonAdapter(aClass.class)способ указать объект JsonDeserializer или объект JsonSerializer, который может играть ту же роль и иметь более высокий приоритет, чемGsonBuilder.registerTypeAdapterимеет более высокий приоритет, поскольку толькоregisterTypeAdapterВыполнение метода упрощено до метода аннотации, который не будет здесь демонстрироваться, непосредственно в предыдущей статье.пользовательская десериализацияВы можете увидеть эффект, используя его в классе Result в разделе.
Эпилог
В этой статье в основном изучается и обобщается использование операций сериализации и десериализации платформы Gson, а также рассказывается об использовании различных функций Gson, надеясь помочь небольшим партнерам, у которых возникают головные боли при работе с данными JSON.