предисловие
В эти выходные была одна и та же публичная статья нескольких технических блоггеров.Еще одна уязвимость была обнаружена в fastjson, на этот раз повреждение может привести к параличу сервиса!Проведите пальцем по экрану, вскоре после предыдущего инцидента с уязвимостью, в FastJson есть еще одна серьезная ошибка. В настоящее время многие проекты используют FastJson для преобразования объектов и данных JSON, и их необходимо обновлять и повторно развертывать, что, можно сказать, требует много времени и труда. В то же время это также натолкнуло меня на новое мышление.Ввиду большого количества мощных библиотек с открытым исходным кодом мы не можем слепо внедрять их в проект.Любой фактор нестабильности во многих фреймворках с открытым исходным кодом достаточен, чтобы вызвать катастрофу для проект. Воспользовавшись выходными, буду дома изучать Gson, отличный фреймворк с открытым исходным кодом, который также имеет функцию взаимного преобразования объектов JSON, и я планирую постепенно заменить использование FastJson в будущих проектах на Gson, и записывать содержимое обучение и резюме, надеясь помочь своим друзьям.
Все фрагменты кода, задействованные в этой статье, находятся на следующем складе. Заинтересованные партнеры могут ознакомиться с ними и изучить их:
https://github.com/wrcj12138aaa/gson-actions
Поддержка версий:
- JDK 8
- gson 2.8.5
- junit 5.5.1
- Lomok 1.18.8
Введение в Гсон
Прежде чем официально представить Gson, мы можем взглянуть на описание Gson из официальной вики, чтобы понять, что это такое?
Gson — это библиотека Java, которую можно использовать для преобразования объектов Java в их представление JSON, а также для преобразования строки JSON в эквивалентный объект Java.
Как видно из описания, Gson — это библиотека Java для преобразования объектов Java в строковые данные в формате JSON и обратно. Первоначально он широко использовался в Google на платформе Android и на сервере Java. После открытия исходного кода в 2008 году он стал еще одним широко используемым фреймворком с открытым исходным кодом для Google.(2019.09.08)На GitHub есть более 1W6 звезд, а библиотеки классов с той же функцией включают Jackson, интегрированный в Spring Framework, и FastJson с открытым исходным кодом от Alibaba.
С точки зрения использования, Gson предоставляет простой APIfromJson/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 по умолчанию будет игнорировать поля с нулевыми значениями при сериализации объектов и выполнятьcom.google.gson.GsonBuilder#serializeNullsПосле метода объекту Gson будет разрешено сериализовать нулевое поле, а обычная сериализованная строка JSON находится в компактном формате, экономя строковую память, используяcom.google.gson.GsonBuilder#setPrettyPrintingОкончательная выходная строка JSON после метода имеет более читаемый формат. Конечно, в дополнение к этим двум методам GsonBuilder также предоставляет множество API-интерфейсов для настройки поведения сериализации и десериализации, которые мы объясним далее в следующих разделах.
JosnObject генерирует JSON
В дополнение к вышеперечисленным методам преобразования объектов пользовательских классов в JSON вы также можете использовать JsonObject, предоставляемый инфраструктурой Gson, для построения обычных объектов, а затем использоватьtoJsonМетод генерирует строку JSON, дополняет приведенный ниже тестовый класс в исходном тестовом классе и запускает его, чтобы увидеть результат следующим образом.
@Test
void test_jsonObject_serialization() {
Gson gson = new Gson();
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("code", 400);
jsonObject.addProperty("message", "参数错误");
String toJson = gson.toJson(jsonObject);
String exceptedJson = "{\"code\":400,\"message\":\"参数错误\"}";
Assertions.assertEquals(exceptedJson, toJson); //true
}
Использование 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 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, которые очень просты в использовании:
@Test
void test_map() {
String jsonString = "{'employee.name':'one','employee.salary':10}";
Gson gson = new Gson();
Map map = gson.fromJson(jsonString, Map.class);
assertEquals(2, map.size());
assertEquals("one", map.get("employee.name"));
assertEquals(Double.class, map.get("employee.salary").getClass());
}
Следует отметить, что реальный тип преобразованного объекта Map — это не HashMap, который мы часто используем, а пользовательская коллекция Gson.LinkedTreeMap, он реализует интерфейс Map, хранит пары ключ-значение, оптимизирует реализацию добавления и удаления и рассматривает порядок хранения пар ключ-значение в качестве порядка обхода, то есть первое сохраненное просматривается первым. Кроме того, числовые данные в строке JSON будут преобразованы в тип Double, иtrue/falseДанные будут преобразованы в логический тип, конкретная основа суждения может относиться кcom.google.gson.internal.bind.ObjectTypeAdapter#readреализация метода.
JSON и массив, преобразование списка
Преобразование JSON в массив
Когда мы выполняем преобразование массива данных JSON, оно похоже на обычное преобразование объектов.toJsonметод используется непосредственно для преобразования в данные JSON; используйтеfromJsonПреобразует указанный тип массива в массив соответствующего типа.
@Test
void test_array() {
Gson gson = new Gson();
int[] ints = {1, 2, 3, 4, 5};
String[] strings = {"abc", "def", "ghi"};
String s = gson.toJson(ints);// [1,2,3,4,5]
assertEquals("[1,2,3,4,5]", s); // true
String s1 = gson.toJson(strings);// ["abc", "def", "ghi"]
assertEquals("[\"abc\",\"def\",\"ghi\"]", s1);
String[] strings1 = gson.fromJson(s1, String[].class);
assertEquals(strings.length, strings1.length); // true
assertEquals(strings[0], strings1[0]); // true
int[] ints2 = gson.fromJson("[1,2,3,4,5]", int[].class);
assertEquals(1, ints2[0]); // true
assertEquals(5, ints2[4]); // true
}
Список конверсий JSON
Чтобы преобразовать данные списка в данные JSON, используйте Gson так же, как данные массива; хотя операция преобразования данных JSON в объект списка немного отличается, при преобразовании данных массива JSON в пользовательский класс List мы пишем следующее:
@Test
public void givenJsonString_whenIncorrectDeserializing() {
Gson gson = new Gson();
String inputString = "[{\"id\":1,\"name\":\"one\"},{\"id\":2,\"name\":\"two\"}]";
List<Person> outputList = gson.fromJson(inputString, List.class);
outputList.get(0).getId();
}
Но, к сожалению, после запуска этого кода он выдаетClassCastExceptionИсключение описывается следующим образом:
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to com.one.learn.Person
...
Из приведенного выше описания мы можем знать, что выполнениеfromJsonПосле этого тип элемента List, полученный после десериализации, будет LinkedTreeMap, а не Person, поэтому при доступе к свойству id как к объекту Person он выдастClassCastExceptionаномальный. Итак, как с этим бороться, нам нужно использовать еще один из Gson'sfromJsonметод: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>() {}.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);
// {"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 указывает имя, используемое при сериализации и десериализации поля-члена, чтобы мы могли скорректировать несоответствие между данными JSON и соответствующим именем поля класса сущности.Ниже приведен конкретный метод использования:
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.
использованная литература
- https://github.com/google/gson/blob/master/UserGuide.md
- https://www.jianshu.com/p/e740196225a4
- https://juejin.cn/post/6844903577098387464
- https://www.baeldung.com/gson-deserialization-guide
- https://www.baeldung.com/gson-string-to-jsonobject
●Spring-boot-starter-grpc Тестирование производительности и выбор различных методов сериализации
●Spring Boot 2 интегрирует структуру ведения журнала log4j2.
●Основной справочный ответ из сборника баллов прохождения собеседования по Java
●Справочный ответ к рамочной статье сводного набора точек прохождения собеседования по Java
●Практичная галантерея Spring Security: как защитить пароли пользователей
●Spring Boot RabbitMQ — приоритетная очередь
●Наглядность и упорядоченность, Бывает-прежде чем это сделать
●Начните с JUnit 5 модульного тестирования Java
●Некоторые расширенные вопросы интервью Spring Boot, необходимые для высокой зарплаты
●Знакомство с ядром Prometheus, сборщика индикаторов мониторинга приложений нового поколения.
●Аннотация @Async в Spring реализует асинхронный вызов «методов».
●Уровень абстракции кэша Боевая операция кэша Spring
●Кратко опишите принципы шаблонов проектирования
●Существует три источника одновременных ошибок, пожалуйста, следите за ними.
●Объяснение шаблона проектирования цепочки ответственности
●Обязательные вопросы о Spring MVC в веб-интервью Java, не собирайте кровопотерю!
●Использование Kafka в микросервисах Micronaut
●В дополнение к zuul и spring облачным шлюзам есть лучшие шлюзы для микросервисов.
●RSocket — альтернатива протоколу Http
●Числа в java и как определить, является ли строка числом или нет
●Чтобы хорошо изучить Spring Security и Apache Shiro, вам необходимо выполнить эти условия
Эта статья опубликована в блогеOpenWriteвыпуск!