Бросьте FastJson! Одной статьи достаточно, функция Джексона настолько хороша (галантерея 4D)

задняя часть

В предыдущей статье "После долгих исследований мы наконец решили отключить FastJson!», рассказал об основном использовании FastJson и существующих неопределенностях, поэтому я, наконец, решил отказаться от использования в проекте, а затем выбрал более популярную библиотеку классов JSON, привязанную к Spring Boot по умолчанию: Jackson.

В этой статье объясняется основное использование Jackson, его комбинация и практика со Spring Boot.

Что такое Джексон

Jackson — относительно популярная библиотека классов JSON на основе Java, которую можно использовать для сериализации и десериализации между Json и XML и JavaBean.

Да, Jackson также может выполнять преобразование между JavaBean и XML на основе компонента jackson-dataformat-xml, и это более эффективно и безопасно, чем собственная реализация XML в JDK. И то, что мы используем больше, — это работа с функцией между JSON и JavaBean.

Насколько популярен Джексон? Судя по статистике в репозитории Maven, использование Джексона занимает первое место. Среди трех библиотек JSON (Gson, Jackson, JSON-B), поддерживаемых Spring Boot, Jackson является предпочтительной библиотекой по умолчанию.

Джексон также имеет следующие характеристики: меньше зависимостей, простота в использовании, быстрый анализ больших JSON, низкое использование памяти, гибкий API, простое расширение и настройка.

Адрес GitHub библиотеки классов Джексона:GitHub.com/faster XML/Просто….

Компоненты Джексона

Основной модуль Джексона состоит из трех частей (начиная с Джексона 2.x): jackson-core, jackson-annotations, jackson-databind.

  • jackson-core: основной пакет, определяющий низкоуровневый Streaming API, обеспечивает синтаксический анализ на основе «потокового режима». Внутренняя реализация Jackson заключается в создании и анализе json с помощью JsonGenerator и JsonParser высокопроизводительного API потокового режима.
  • jackson-annotations, пакет Annotations, предоставляет стандартные функции аннотации Джексона;
  • jackson-databind: пакет Databind реализует поддержку привязки данных (и сериализации объектов) и опирается на пакеты Streaming и Annotations. Предоставляет синтаксический анализ API (ObjectMapper) на основе «привязки объекта» и API (JsonNode) синтаксического анализа «модель дерева»; синтаксический анализ API на основе «привязки объекта» и анализ «модель дерева» зависит от синтаксического анализа на основе API «потокового режима».

Давайте посмотрим на введение зависимостей связанных компонентов в разных средах.

В SpringBoot spring-boot-starter-web косвенно вводит компоненты Jackson, то есть, если вы используете фреймворк SpringBoot, то в вашем проекте уже есть зависимости Jackson. Следующие зависимости опускают элементы версии и области действия.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Веб-стартер полагается на стартер json:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-json</artifactId>
</dependency>

Стартер json в конце концов представляет Джексона:

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.module</groupId>
  <artifactId>jackson-module-parameter-names</artifactId>
</dependency>

Как упоминалось выше, jackson-databind зависит от пакетов Streaming и Annotations, поэтому введение jackson-databind эквивалентно введению jackson-core и jackson-annotations.

Обычно, когда мы используем его отдельно, мы можем ввести jackson-databind, jackson-core и jackson-annotations через Maven по мере необходимости.

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>

Для проектов SpringBoot практически нет необходимости добавлять дополнительные зависимости.

Базовый класс Джексона ObjectMapper

Джексон предоставляет три метода обработки JSON, а именно: привязку данных, древовидную модель JSON и потоковый API. Первые две функции реализованы на основе ObjectMapper, а функции потокового API необходимо реализовать на основе JsonGenerator и JsonParser более низкого уровня.

Обычно достаточно использовать класс ObjectMapper, который имеет следующие функции:

  • Разобрать JSON из строки, потока или файла и создать объект Java (десериализовать), который представляет проанализированный JSON.
  • Встроить объект Java в строку JSON (сериализовать).
  • Разбирать JSON на объекты пользовательских классов, а также может анализировать объекты древовидной модели JSON;

ObjectMapper реализует фактическое чтение/запись JSON на основе JsonParser и JsonGenerator. Этот момент можно понять, взглянув на конструктор ObjectMapper.

конкретные примеры

Распространенные случаи использования имени Джексона не будут объясняться по одному, я покажу вам серию примеров, и каждый пример будет сопровождаться комментариями.

Обычный и простой в использовании

Следующий пример представляет собой демонстрацию использования, которое мы часто используем, в основном с преобразованием между строками JavaBean и Json.

Когда Джексон преобразует json в свойства JavaBean, по умолчанию имя поля Json сопоставляется с методами получения и установки в объекте Java для привязки.

Джексон удаляет части «get» и «set» из имен методов получения и установки и переводит первую букву в нижний регистр. Например, имя в Json соответствует getName() и setName() в JavaBean.

Но не все свойства можно сериализовать и десериализовать, в основном следуйте правилам:

  • Общедоступные измененные свойства можно сериализовать и десериализовать.
  • Свойство предоставляет общедоступные методы получения/установки, которые можно сериализовать и десериализовать.
  • Свойство имеет только общедоступный метод установки, но не общедоступный метод получения, и свойство может использоваться только для десериализации.
@Slf4j
public class JacksonTest {

    /**
     * JavaBean转JSON字符串
     */
    @Test
    public void testJavaBeanToJson() {
        WeChat weChat = new WeChat();
        weChat.setId("zhuan2quan");
        weChat.setName("程序新视界");
        weChat.setInterest(new String[]{"Java", "Spring Boot", "JVM"});

        ObjectMapper mapper = new ObjectMapper();
        try {
            String result = mapper.writeValueAsString(weChat);
            System.out.println(result);
        } catch (JsonProcessingException e) {
            log.error("转换异常", e);
        }
    }

    /**
     * JSON字符串转JavaBean
     */
    @Test
    public void testJsonToJavaBean() {
        String json = "{\"id\":\"zhuan2quan\",\"name\":\"程序新视界\",\"interest\":[\"Java\",\"Spring Boot\",\"JVM\"]}";
        ObjectMapper mapper = new ObjectMapper();
        try {
            WeChat weChat = mapper.readValue(json, WeChat.class);
            System.out.println(weChat);
        } catch (JsonProcessingException e) {
            log.error("解析异常", e);
        }
    }

    /**
     * JSON字符串转Map集合
     */
    @Test
    public void testJsonToMap() {
        String json = "{\"id\":\"zhuan2quan\",\"name\":\"程序新视界\",\"interest\":[\"Java\",\"Spring Boot\",\"JVM\"]}";
        ObjectMapper mapper = new ObjectMapper();
        try {
            // 对泛型的反序列化,使用TypeReference可以明确的指定反序列化的类型。
            Map<String, Object> map = mapper.readValue(json, new TypeReference<Map<String, Object>>() {
            });
            System.out.println(map);
        } catch (JsonProcessingException e) {
            log.error("解析异常", e);
        }
    }

    /**
     * JavaBean转文件
     */
    @Test
    public void testJavaBeanToFile() {
        WeChat weChat = new WeChat();
        weChat.setId("zhuan2quan");
        weChat.setName("程序新视界");
        weChat.setInterest(new String[]{"Java", "Spring Boot", "JVM"});

        ObjectMapper mapper = new ObjectMapper();
        try {
            //写到文件
            mapper.writeValue(new File("/json.txt"), weChat);

            //从文件中读取
            WeChat weChat1 = mapper.readValue(new File("/json.txt"), WeChat.class);
            System.out.println(weChat1);
        } catch (IOException e) {
            log.error("转换异常", e);
        }
    }

    /**
     * JavaBean转字节流
     */
    @Test
    public void testJavaBeanToBytes() {
        WeChat weChat = new WeChat();
        weChat.setId("zhuan2quan");
        weChat.setName("程序新视界");
        weChat.setInterest(new String[]{"Java", "Spring Boot", "JVM"});

        ObjectMapper mapper = new ObjectMapper();
        try {
            // 写为字节流
            byte[] bytes = mapper.writeValueAsBytes(weChat);
            // 从字节流读取
            WeChat weChat1 = mapper.readValue(bytes, WeChat.class);
            System.out.println(weChat1);
        } catch (IOException e) {
            log.error("转换异常", e);
        }
    }
}

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

Модель дерева JSON

Если строка Json относительно велика, модель дерева JSON можно использовать для гибкого получения необходимого содержимого поля. В Джексоне для получения или оценки предусмотрены такие методы, как get, path и has.

Давайте посмотрим непосредственно на два примера:

@Slf4j
public class JacksonNodeTest {

    /**
     * JavaBean转JSON字符串
     */
    @Test
    public void testJsonNode() {
        // 构建JSON树
        ObjectMapper mapper = new ObjectMapper();
        ObjectNode root = mapper.createObjectNode();
        root.put("id", "zhuan2quan");
        root.put("name", "程序新视界");
        ArrayNode interest = root.putArray("interest");
        interest.add("Java");
        interest.add("Spring Boot");
        interest.add("JVM");

        // JSON树转JSON字符串
        String json = null;
        try {
            json = mapper.writeValueAsString(root);
        } catch (JsonProcessingException e) {
            log.error("Json Node转换异常", e);
        }
        System.out.println(json);
    }

    /**
     * 解析JSON字符串为JSON树模型
     */
    @Test
    public void testJsonToJsonNode() {
        String json = "{\"id\":\"zhuan2quan\",\"name\":\"程序新视界\",\"interest\":[\"Java\",\"Spring Boot\",\"JVM\"]}";
        ObjectMapper mapper = new ObjectMapper();
        try {
            // 将JSON字符串转为JSON树
            JsonNode jsonNode = mapper.readTree(json);
            String name = jsonNode.path("name").asText();
            System.out.println(name);

            JsonNode interestNode = jsonNode.get("interest");
            if (interestNode.isArray()){
                for (JsonNode node : interestNode){
                    System.out.println(node.asText());
                }
            }
        } catch (JsonProcessingException e) {
            log.error("Json Node转换异常", e);
        }
    }
}

Метод get похож на функцию path. Разница в том, что если ключ, который нужно прочитать, не существует в строке Json, метод get будет иметь значение null, а путь вернет объект экземпляра MissingNode. link, гарантируется, что исключение не будет выдано.

Потоковое API

В дополнение к двум вышеупомянутым формам операции также могут выполняться на основе базового потокового API, в основном через два API JsonGenerator и JsonParser, но эта операция более сложная и здесь не будет продемонстрирована.

Формат единой конфигурации

При использовании ObjectMapper будут некоторые поля, которые в некоторых случаях не нужно сериализовать или десериализовать, а также может потребоваться указание некоторой информации о форматировании. На данный момент его можно настроить через ObjectMapper.

//反序列化时忽略json中存在但Java对象不存在的属性
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//序列化时日期格式默认为yyyy-MM-dd'T'HH:mm:ss.SSSZ
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
//序列化时自定义时间日期格式
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
//序列化时忽略值为null的属性
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
//序列化时忽略值为默认值的属性
mapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_DEFAULT);

Для элементов конфигурации в версии 2.2 был добавлен новый класс реализации ObjectMapper, JsonMapper, с той же функцией, что и ObjectMapper. Но был добавлен новый метод построения. Вы можете настроить его напрямую с помощью метода JsonMapper.builder().configure() и, наконец, получить объект JsonMapper. Другие методы JsonMapper в основном интегрированы с ObjectMapper.

Использование аннотаций

Сериализацию и десериализацию глобального формата можно настроить с помощью унифицированной конфигурации, описанной выше, но в некоторых отдельных сценариях необходимо настроить определенные поля, для чего требуются аннотации. Например, когда поля в строке Json не соответствуют свойствам в объекте Java, необходимо установить их прямую связь через аннотации.

@JsonProperty, воздействуя на поля JavaBean, указывает поле для сопоставления JSON.По умолчанию сопоставленное поле JSON имеет то же имя, что и аннотированное поле. Имя поля сопоставленного JSON можно указать через атрибут value.

@JsonIgnore можно использовать для полей, методов получения/установки и параметров конструктора, указав, что поля не участвуют в сериализации и десериализации.

@JsonIgnoreProperties воздействует на класс, а @JsonIgnoreProperties({"prop1", "prop2"}) игнорирует два свойства pro1 и pro2 во время сериализации. @JsonIgnoreProperties(ignoreUnknown=true) игнорирует поля, не существующие в классе, при десериализации.

@JsonFormat действует на поля и обычно используется для операций форматирования.

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date date;

Если поле времени в JavaBean использует новый тип времени и даты (LocalDate/LocalTime/LocalDateTime) JDK8, вам необходимо добавить зависимость jackson-datatype-jsr310. Говоря о части зависимостей, это зависимость, представленная SpringBoot по умолчанию.

Конечно, есть и некоторые другие аннотации, такие как @JsonPropertyOrder, @JsonRootName, @JsonAnySetter, @JsonAnyGetter, @JsonNaming и т. д. При их использовании вы можете обратиться к соответствующим документам и примерам, чтобы посмотреть их, и они не будут быть перечислены здесь.

пользовательский парсер

Если приведенные выше аннотации и унифицированная конфигурация не могут удовлетворить потребности, вы можете настроить парсер, пример выглядит следующим образом:

public class MyFastjsonDeserialize extends JsonDeserializer<Point> {

    @Override
    public Point deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        JsonNode node = jsonParser.getCodec().readTree(jsonParser);
        Iterator<JsonNode> iterator = node.get("coordinates").elements();
        List<Double> list = new ArrayList<>();
        while (iterator.hasNext()) {
            list.add(iterator.next().asDouble());
        }
        return new Point(list.get(0), list.get(1));
    }
}

После завершения определения зарегистрируйтесь в Mapper:

ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Point.class, new MyFastjsonDeserialize());
objectMapper.registerModule(module);

Джексон обрабатывает XML

Джексон также предоставляет функциональные возможности для обработки XML с помощью пакета jackson-dataformat-xml. При работе с XML рекомендуется использовать пакет woodstox-core, представляющий собой реализацию XML, более эффективную и безопасную, чем собственная реализация XML JDK.

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

Если вы используете Java 9 и более поздние версии, может возникнуть ошибка java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException, поскольку Java 9 реализует модульность JDK и отделяет реализацию JAXB, которая изначально была упакована с JDK. Поэтому вам нужно вручную добавить реализацию JAXB.

<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
</dependency>

Ниже приведен пример кода, в основном очень похожий на JSON API, XmlMapper на самом деле является подклассом ObjectMapper.

@Test
public void testXml(){
    WeChat weChat = new WeChat();
    weChat.setId("zhuan2quan");
    weChat.setName("程序新视界");
    weChat.setInterest(new String[]{"Java", "Spring Boot", "JVM"});

    XmlMapper xmlMapper = new XmlMapper();
    try {
        String xml = xmlMapper.writeValueAsString(weChat);
        System.out.println(xml);
    } catch (JsonProcessingException e) {
        e.printStackTrace();
    }
}

После выполнения результат вывода:

<WeChat>
    <id>zhuan2quan</id>
    <name>程序新视界</name>
    <interest>
        <interest>Java</interest>
        <interest>Spring Boot</interest>
        <interest>JVM</interest>
    </interest>
</WeChat>

Интеграция в Spring Boot

В самом начале мы видели, что Spring Boot по умолчанию вводит зависимости Джексона, а также мы делаем с нами дополнительные операции, по сути, мы уже используем Джексон для привязки данных в формате Json к параметрам в MVC.

Если конфигурация Spring Boot по умолчанию не подходит для требований проекта, ее также можно настроить через встроенную конфигурацию.На примере конфигурации application.yml соответствующие параметры можно настроить, указав следующие свойства:

#指定日期格式,比如yyyy-MM-dd HH:mm:ss,或者具体的格式化类的全限定名
spring.jackson.date-format 
#是否开启Jackson的反序列化
spring.jackson.deserialization
#是否开启json的generators.
spring.jackson.generator
#指定Joda date/time的格式,比如yyyy-MM-ddHH:mm:ss). 如果没有配置的话,dateformat会作为backup
spring.jackson.joda-date-time-format
#指定json使用的Locale.
spring.jackson.locale
#是否开启Jackson通用的特性.
spring.jackson.mapper
#是否开启jackson的parser特性.
spring.jackson.parser
#指定PropertyNamingStrategy(CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES)或者指定PropertyNamingStrategy子类的全限定类名.
spring.jackson.property-naming-strategy
#是否开启jackson的序列化.
spring.jackson.serialization
#指定序列化时属性的inclusion方式,具体查看JsonInclude.Include枚举.
spring.jackson.serialization-inclusion
#指定日期格式化时区,比如America/Los_Angeles或者GMT+10.
spring.jackson.time-zone

Автоматическая настройка Spring Boot очень удобна, но иногда нам нужно настроить bean-компоненты вручную, чтобы заменить автоматически настроенные bean-компоненты. Его можно настроить в следующем виде:

@Configuration
public class JacksonConfig {
    @Bean
    @Qualifier("json")
    public ObjectMapper jsonMapper(Jackson2ObjectMapperBuilder builder) {
        ObjectMapper mapper = builder.createXmlMapper(false)
                .build();
        mapper.enable(SerializationFeature.INDENT_OUTPUT);
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        return mapper;
    }
}

После того, как конфигурация завершена, ее можно внедрить непосредственно туда, где она используется:

@Resource
private ObjectMapper jsonMapper;

Для приведенной выше инъекции некоторые друзья могут спросить, есть ли проблема безопасности потоков? Это не беспокоит ObjectMapper потокобезопасен.

резюме

После объяснения этой статьи у вас должно быть более полное представление о Джексоне. Лично после изучения Джексона это кажется довольно интересным.

Оригинальная ссылка: "Бросьте FastJson! Одной статьи достаточно, функция Джексона настолько хороша (галантерея 4D)


Программа Новые Горизонты

\

Нет публики"Программа Новые Горизонты», платформа, которая позволяет вам одновременно улучшать свою мягкую силу и жесткую технологию, предоставляя массивные данные

\