Configurando ObjectMapper no Spring

95

meu objetivo é configurar o de objectMapperforma que ele apenas serialize elementos que são anotados com @JsonProperty.

Para fazer isso, segui esta explicação que diz como configurar o mapeador de objetos.

Incluí o mapeador de objetos personalizado conforme descrito aqui .

No entanto, quando a classe NumbersOfNewEventsé serializada, ela ainda contém todos os atributos no json.

Alguém tem uma dica? desde já, obrigado

Jackson 1.8.0 spring 3.0.5

CustomObjectMapper

public class CompanyObjectMapper extends ObjectMapper {
    public CompanyObjectMapper() {
        super();
        setVisibilityChecker(getSerializationConfig()
                .getDefaultVisibilityChecker()
                .withCreatorVisibility(JsonAutoDetect.Visibility.NONE)
                .withFieldVisibility(JsonAutoDetect.Visibility.NONE)
                .withGetterVisibility(JsonAutoDetect.Visibility.NONE)
                .withIsGetterVisibility(JsonAutoDetect.Visibility.NONE)
                .withSetterVisibility(JsonAutoDetect.Visibility.DEFAULT));
    }
}

servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">

    <context:component-scan base-package="de.Company.backend.web" />

    <mvc:annotation-driven />

    <bean
        class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
        <property name="messageConverters">
            <list>
                <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
                    <property name="objectMapper" ref="jacksonObjectMapper" />
                </bean>
            </list>
        </property>
    </bean>

    <bean id="jacksonObjectMapper" class="de.Company.backend.web.CompanyObjectMapper" />
</beans>

NumbersOfNewEvents

public class NumbersOfNewEvents implements StatusAttribute {

    public Integer newAccepts;
    public Integer openRequests;

    public NumbersOfNewEvents() {
        super();
    }
}
Steve Eastwood
fonte

Respostas:

140

Usando Spring Boot (1.2.4) e Jackson (2.4.6), a seguinte configuração baseada em anotação funcionou para mim.

@Configuration
public class JacksonConfiguration {

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true);

        return mapper;
    }
}
Alex
fonte
3
Spring oferece suporte à personalização de algumas propriedades de Jackson em application.properties: docs.spring.io/spring-boot/docs/current/reference/html/…
NangSaigon
10
Ei pessoal. Eu enfrentei um problema que quando você declara o bean com o nome objectMapper na configuração do java, assim como é mostrado no exemplo, este bean é ignorado pelo Spring Boot, pois o bean com este nome já está registrado no sistema, mesmo se você colocar \ @ Anotação primária nele. Para corrigir este problema, você precisa registrar o bean com um nome diferente, por exemplo, jsonObjectMapper, e marcá-lo com a anotação \ @Primary
Demwis
Consulte minha resposta se você não estiver usando o Spring Boot.
Leirão
Uma pergunta: você deve nomear a classe JacksonConfiguration? É uma maneira automática de instruir o Spring Boot a usar essa classe como classe de configuração para Jackson?
Marco Sulla
Está realmente funcionando ao usar também @EnableWebMvc? Duvido porque ... veja na minha resposta : Por que o uso de @EnableWebMvc é um problema?
adrhc
26

Pode ser porque estou usando Spring 3.1 (em vez de Spring 3.0.5 como sua pergunta especificou), mas a resposta de Steve Eastwood não funcionou para mim. Esta solução funciona para Spring 3.1:

Em seu contexto xml de primavera:

<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
        <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
        <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
            <property name="objectMapper" ref="jacksonObjectMapper" />
        </bean>        
    </mvc:message-converters>
</mvc:annotation-driven>

<bean id="jacksonObjectMapper" class="de.Company.backend.web.CompanyObjectMapper" />
Andrew Newdigate
fonte
Isso ajudou a usar Jackson 2.x com Spring 3.1: stackoverflow.com/questions/10420040/…
Aram Kocharyan
Útil! Tive que adotar essa abordagem para registrar meu ObjectMapper customizado na atualização do Spring 3.0 para 3.2. Anteriormente, configurava uma propriedade objectMapper ao definir um bean MappingJacksonJsonView, mas isso não funcionava mais. Isto é com Jackson 1.7
David Easley
20

org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBeanmuito tempo. A partir da versão 1.2 do Spring Boot, há org.springframework.http.converter.json.Jackson2ObjectMapperBuilderpara Java Config.

Em String Boot, a configuração pode ser tão simples como:

spring.jackson.deserialization.<feature_name>=true|false
spring.jackson.generator.<feature_name>=true|false
spring.jackson.mapper.<feature_name>=true|false
spring.jackson.parser.<feature_name>=true|false
spring.jackson.serialization.<feature_name>=true|false
spring.jackson.default-property-inclusion=always|non_null|non_absent|non_default|non_empty

em classpath:application.propertiesou algum código Java na @Configurationclasse:

@Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() {
    Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
    builder.indentOutput(true).dateFormat(new SimpleDateFormat("yyyy-MM-dd"));
    return builder;
}

Vejo:

Gavenkoa
fonte
2
Isso funciona para a maioria dos casos de uso, no entanto, a configuração do Spring Boot jackson é ignorada se você estiver usando a anotação @EnableWebMvc (testada no Spring Boot 2.0.3).
John
1
Embora eu não tenha testado, tenho certeza de que não funcionará com @EnableWebMvc (apesar de usar ou não a inicialização com mola). Por quê? ver WebMvcConfigurationSupport (DelegatingWebMvcConfiguration estende WebMvcConfigurationSupport): Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json().
adrhc
16

Eu usei isso com Jackson 2.xe Spring 3.1.2+

servlet-context.xml:

Observe que o elemento raiz é <beans:beans>, portanto, pode ser necessário remover beanse adicionar mvcalguns desses elementos, dependendo de sua configuração.

    <annotation-driven>
        <message-converters>
            <beans:bean
                class="org.springframework.http.converter.StringHttpMessageConverter" />
            <beans:bean
                class="org.springframework.http.converter.ResourceHttpMessageConverter" />
            <beans:bean
                class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                <beans:property name="objectMapper" ref="jacksonObjectMapper" />
            </beans:bean>
        </message-converters>
    </annotation-driven>

    <beans:bean id="jacksonObjectMapper"
        class="au.edu.unimelb.atcom.transfer.json.mappers.JSONMapper" />

au.edu.unimelb.atcom.transfer.json.mappers.JSONMapper.java:

public class JSONMapper extends ObjectMapper {

    public JSONMapper() {
        SimpleModule module = new SimpleModule("JSONModule", new Version(2, 0, 0, null, null, null));
        module.addSerializer(Date.class, new DateSerializer());
        module.addDeserializer(Date.class, new DateDeserializer());
        // Add more here ...
        registerModule(module);
    }

}

DateSerializer.java:

public class DateSerializer extends StdSerializer<Date> {

    public DateSerializer() {
        super(Date.class);
    }

    @Override
    public void serialize(Date date, JsonGenerator json,
            SerializerProvider provider) throws IOException,
            JsonGenerationException {
        // The client side will handle presentation, we just want it accurate
        DateFormat df = StdDateFormat.getBlueprintISO8601Format();
        String out = df.format(date);
        json.writeString(out);
    }

}

DateDeserializer.java:

public class DateDeserializer extends StdDeserializer<Date> {

    public DateDeserializer() {
        super(Date.class);
    }

    @Override
    public Date deserialize(JsonParser json, DeserializationContext context)
            throws IOException, JsonProcessingException {
        try {
            DateFormat df = StdDateFormat.getBlueprintISO8601Format();
            return df.parse(json.getText());
        } catch (ParseException e) {
            return null;
        }
    }

}
Aram Kocharyan
fonte
10

Encontrei a solução agora baseada em https://github.com/FasterXML/jackson-module-hibernate

Estendi o mapeador de objetos e adicionei os atributos no construtor herdado.

Em seguida, o novo mapeador de objetos é registrado como um bean.

<!-- https://github.com/FasterXML/jackson-module-hibernate -->
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="messageConverters">
        <array>
            <bean id="jsonConverter"
            class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
                <property name="objectMapper">
                    <bean class="de.company.backend.spring.PtxObjectMapper"/>
                </property>
            </bean>
        </array>
    </property>
</bean>   
Steve Eastwood
fonte
Muito obrigado pela solução! Gastei mais de quatro horas cavando em torno de <annotation-driven> , ele simplesmente não pegou os conversores. Tentei sua resposta, finalmente funciona!
snowindy de
1
Para usar anotações, você pode apenas anotar seu mapeador personalizado que se estende ObjectMappercomo @Componente @Primary. Essa é a abordagem recomendada no Guia de Referência do Spring e funcionou bem para mim.
OlgaMaciaszek
9

Se você deseja incluir ObjectMapper customizado para registrar serializadores customizados, tente minha resposta.

No meu caso (Spring 3.2.4 e Jackson 2.3.1), configuração XML para serializador personalizado:

<mvc:annotation-driven>
    <mvc:message-converters register-defaults="false">
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <property name="objectMapper">
                <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
                    <property name="serializers">
                        <array>
                            <bean class="com.example.business.serializer.json.CustomObjectSerializer"/>
                        </array>
                    </property>
                </bean>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

foi de forma inexplicável substituída de volta ao padrão por algo.

Isso funcionou para mim:

CustomObject.java

@JsonSerialize(using = CustomObjectSerializer.class)
public class CustomObject {

    private Long value;

    public Long getValue() {
        return value;
    }

    public void setValue(Long value) {
        this.value = value;
    }
}

CustomObjectSerializer.java

public class CustomObjectSerializer extends JsonSerializer<CustomObject> {

    @Override
    public void serialize(CustomObject value, JsonGenerator jgen,
        SerializerProvider provider) throws IOException,JsonProcessingException {
        jgen.writeStartObject();
        jgen.writeNumberField("y", value.getValue());
        jgen.writeEndObject();
    }

    @Override
    public Class<CustomObject> handledType() {
        return CustomObject.class;
    }
}

Nenhuma configuração XML ( <mvc:message-converters>(...)</mvc:message-converters>) é necessária em minha solução.

user11153
fonte
7

SOLUÇÃO 1

Primeira solução de trabalho (testada) útil especialmente ao usar @EnableWebMvc:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private ObjectMapper objectMapper;// created elsewhere
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        // this won't add a 2nd MappingJackson2HttpMessageConverter 
        // as the SOLUTION 2 is doing but also might seem complicated
        converters.stream().filter(c -> c instanceof MappingJackson2HttpMessageConverter).forEach(c -> {
            // check default included objectMapper._registeredModuleTypes,
            // e.g. Jdk8Module, JavaTimeModule when creating the ObjectMapper
            // without Jackson2ObjectMapperBuilder
            ((MappingJackson2HttpMessageConverter) c).setObjectMapper(this.objectMapper);
        });
    }

SOLUÇÃO 2

É claro que a abordagem comum abaixo também funciona (trabalhando também com @EnableWebMvc):

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private ObjectMapper objectMapper;// created elsewhere
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        // this will add a 2nd MappingJackson2HttpMessageConverter 
        // (additional to the default one) but will work and you 
        // won't lose the default converters as you'll do when overwriting
        // configureMessageConverters(List<HttpMessageConverter<?>> converters)
        // 
        // you still have to check default included
        // objectMapper._registeredModuleTypes, e.g.
        // Jdk8Module, JavaTimeModule when creating the ObjectMapper
        // without Jackson2ObjectMapperBuilder
        converters.add(new MappingJackson2HttpMessageConverter(this.objectMapper));
    }

Por que o uso de @EnableWebMvc é um problema?

@EnableWebMvc está usando o DelegatingWebMvcConfigurationque estende o WebMvcConfigurationSupportque faz isso:

if (jackson2Present) {
    Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
    if (this.applicationContext != null) {
        builder.applicationContext(this.applicationContext);
    }
    messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build()));
}

o que significa que não há como injetar o seu próprio ObjectMappercom o objetivo de prepará-lo para ser usado na criação do padrão MappingJackson2HttpMessageConverterao usar @EnableWebMvc.

adrhc
fonte
Ótimo! Na verdade, RepositoryRestMvcConfiguration, parte de spring-data-rest-webmvc: 3.2.3 não usa ObjectMapper criado pelo Spring e apenas o objectMapper personalizado com módulos não funcionam pronto para uso. A primeira abordagem é real e substitui o ObjectMapper customizado por spring-data-rest-webmvc. Obrigado!
Valtoni Boaventura
Tive um problema em que não conseguia personalizar meu ObjectMapper que estava péssimo com o EnableWebMvc. Que bom que eu tropecei nisso.
Evan White
6

Estou usando Spring 4.1.6 e Jackson FasterXML 2.1.4.

    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                <property name="objectMapper">
                    <bean class="com.fasterxml.jackson.databind.ObjectMapper">
                        <!-- 设置不输出null字段-->
                        <property name="serializationInclusion" value="NON_NULL"/>
                    </bean>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

isso funciona na minha configuração applicationContext.xml

Jecyhw
fonte
4

Para configurar um conversor de mensagem em spring-web simples, neste caso para habilitar o Java 8 JSR-310 JavaTimeModule, primeiro você precisa implementar WebMvcConfigurerem sua @Configurationclasse e então substituir o configureMessageConvertersmétodo:

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().modules(new JavaTimeModule(), new Jdk8Module()).build()
            .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    converters.add(new MappingJackson2HttpMessageConverter(objectMapper));
}

Assim, você pode registrar qualquer definição personalizada ObjectMapperem uma configuração Spring baseada em Java.

Leirão
fonte
Eu preferiria ter o ObjectMapper injetado e configurado via Spring Boot.
Constantino Cronemberger
1
Claro, se você usar Spring Boot. Esta não era uma pergunta do Spring Boot.
Leirão de
Sim, é interessante notar que ao usar o Spring Web com Spring Boot ele não usa o ObjectMapper fornecido pelo Spring Boot e é por isso que acabei nesta questão que me ajudou.
Constantino Cronemberger
Isso limparia todos os conversores de mensagem padrão! Tenho certeza que ninguém quer isso. Veja minha resposta para a solução.
adrhc
Bem, se você quiser estender os conversores de mensagem, a solução correta seria substituir extendMessageConverters, de acordo com a documentação do Spring . Mas não tive necessidade, só preciso de um customizado MappingJackson2HttpMessageConverter.
Leirão
3

No Spring Boot, 2.2.xvocê precisa configurá-lo assim:

@Bean
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
    return builder.build()
}

Kotlin:

@Bean
fun objectMapper(builder: Jackson2ObjectMapperBuilder) = builder.build()
CorayThan
fonte
2

Acima do Spring 4, não há necessidade de configurar MappingJacksonHttpMessageConverterse você apenas pretende configurar ObjectMapper.

(configure MappingJacksonHttpMessageConverterfará com que você perca outro MessageConverter)

Você só precisa fazer:

public class MyObjectMapper extends ObjectMapper {

    private static final long serialVersionUID = 4219938065516862637L;

    public MyObjectMapper() {
        super();
        enable(SerializationFeature.INDENT_OUTPUT);
    }       
}

E em sua configuração Spring, crie este bean:

@Bean 
public MyObjectMapper myObjectMapper() {        
    return new MyObjectMapper();
}
Sam YC
fonte
Não funciona - - customObjectMapper: definido pelo método 'customObjectMapper' no recurso de caminho de classe [com / Config.class] -_halObjectMapper: defined in null
Angshuman Agarwal
1
E isso só funcionará com Spring Boot. Não é simples Spring Web.
Leirão de
1

Estou usando Spring 3.2.4 e Jackson FasterXML 2.1.1.

Eu criei um JacksonObjectMapper personalizado que funciona com anotações explícitas para cada atributo dos objetos mapeados:

package com.test;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

public class MyJaxbJacksonObjectMapper extends ObjectMapper {

public MyJaxbJacksonObjectMapper() {

    this.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
            .setVisibility(PropertyAccessor.CREATOR, JsonAutoDetect.Visibility.ANY)
            .setVisibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.NONE)
            .setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE)
            .setVisibility(PropertyAccessor.IS_GETTER, JsonAutoDetect.Visibility.NONE);

    this.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
    }
}

Então, isso é instanciado na configuração de contexto ( servlet-context.xml ):

<mvc:annotation-driven>
    <mvc:message-converters>

        <beans:bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <beans:property name="objectMapper">
                <beans:bean class="com.test.MyJaxbJacksonObjectMapper" />
            </beans:property>
        </beans:bean>

    </mvc:message-converters>
</mvc:annotation-driven>

Isso funciona bem!

Carlos AG
fonte