Al usar Spring MVC para REST, ¿cómo habilita Jackson para imprimir JSON renderizado?


Mientras desarrollaba servicios REST usando Spring MVC, me gustaría renderizar JSON 'pretty printed' en desarrollo pero normal (espacio en blanco reducido) en producción.

Author: skaffman, 2011-07-01

10 answers

Si está utilizando Spring Boot 1.2 o posterior, la solución simple es agregar

spring.jackson.serialization.INDENT_OUTPUT=true

Al archivo application.properties. Esto asume que está usando Jackson para serialización.

Si está utilizando una versión anterior de Spring Boot, puede agregar

http.mappers.json-pretty-print=true

Esta solución todavía funciona con Spring Boot 1.2, pero está en desuso y eventualmente se eliminará por completo. Recibirá una advertencia de obsolescencia en el registro en el momento del inicio.

(probado usando spring-boot-starter-web)

 37
Author: user4061342,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-15 05:32:05

Tenía una respuesta cuando publiqué esta pregunta, pero pensé que la publicaría de todos modos en caso de que hubiera mejores soluciones alternativas. Aquí estaba mi experiencia:

Lo primero es lo primero. El MappingJacksonHttpMessageConverter espera que inyecte una instancia de Jackson ObjectMapper y realice la configuración de Jackson en esa instancia (y no a través de una clase Spring).

Pensé que sería tan fácil como hacer esto:{[16]]}

Crear una implementación ObjectMapperFactoryBean que me permita personalizar la instancia ObjectMapper que puede ser inyectado en el MappingJacksonHttpMessageConverter. Por ejemplo:

<bean id="jacksonHttpMessageConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
    <property name="objectMapper">
        <bean class="com.foo.my.ObjectMapperFactoryBean">
            <property name="prettyPrint" value="${json.prettyPrint}"/>
        </bean>
    </property>
</bean>

Y luego, en mi ObjectMapperFactoryBean implementación, podría hacer esto (como se ha documentado como una solución en otro lugar sobre SO):

ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, isPrettyPrint());
return mapper;

Pero no funcionó. Y tratando de averiguar por qué es una pesadilla . Es una gran prueba de paciencia descubrir a Jackson. Mirando su código fuente solo confunde aún más, ya que utiliza formas obsoletas y obtusas de configuración (máscaras de bits enteros para activar/desactivar las características? ¿Estás bromeando? ¿yo?)

Esencialmente tuve que reescribir el MappingJacksonHttpMessageConverter de Spring desde cero, y anular su writeInternal implementación para ser la siguiente:

@Override
protected void writeInternal(Object o, HttpOutputMessage outputMessage)
        throws IOException, HttpMessageNotWritableException {

    JsonEncoding encoding = getEncoding(outputMessage.getHeaders().getContentType());
    JsonGenerator jsonGenerator =
            getObjectMapper().getJsonFactory().createJsonGenerator(outputMessage.getBody(), encoding);
    try {
        if (this.prefixJson) {
            jsonGenerator.writeRaw("{} && ");
        }
        if (isPrettyPrint()) {
            jsonGenerator.useDefaultPrettyPrinter();
        }
        getObjectMapper().writeValue(jsonGenerator, o);
    }
    catch (JsonGenerationException ex) {
        throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
    }
}

Lo único que agregué a la implementación existente es el siguiente bloque:

if (isPrettyPrint()) {
    jsonGenerator.useDefaultPrettyPrinter();
}

isPrettyPrint() es solo un getter compatible con JavaBeans con setter coincidente que agregué a mi subclase MappingJacksonHttpMessageConverter.

Solo después de saltar a través de estos aros fui capaz de activar o desactivar la impresión bonita basada en mi ${json.prettyPrint} valor (que se establece como un propiedad en función de cómo se implementa la aplicación).

Espero que esto ayude a alguien en el futuro!

 31
Author: Les Hazlewood,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-07-15 01:25:06

Cuando estás usando Jackson 2.0.0, puedes hacerlo de la manera que Les quería. Actualmente uso RC3 y la configuración parece estar funcionando como se esperaba.

ObjectMapper jacksonMapper = new ObjectMapper();
jacksonMapper.configure(SerializationFeature.INDENT_OUTPUT, true);

Traduce

{"foo":"foo","bar":{"field1":"field1","field2":"field2"}}

Hacia

{
  "foo" : "foo",
  "bar" : {
    "field1" : "field1",
    "field2" : "field2"
  }
}
 23
Author: Swato,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-25 18:54:20

¿Cómo hago que Jackson imprima bastante el contenido JSON que genera?

He aquí un ejemplo sencillo:

Entrada JSON original:

{"one":"AAA","two":["BBB","CCC"],"three":{"four":"DDD","five":["EEE","FFF"]}}

Foo.java:

import java.io.FileReader;

import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ObjectWriter;

public class Foo
{
  public static void main(String[] args) throws Exception
  {
    ObjectMapper mapper = new ObjectMapper();
    MyClass myObject = mapper.readValue(new FileReader("input.json"), MyClass.class);
    // this is Jackson 1.x API only: 
    ObjectWriter writer = mapper.defaultPrettyPrintingWriter();
    // ***IMPORTANT!!!*** for Jackson 2.x use the line below instead of the one above: 
    // ObjectWriter writer = mapper.writer().withDefaultPrettyPrinter();
    System.out.println(writer.writeValueAsString(myObject));
  }
}

class MyClass
{
  String one;
  String[] two;
  MyOtherClass three;

  public String getOne() {return one;}
  void setOne(String one) {this.one = one;}
  public String[] getTwo() {return two;}
  void setTwo(String[] two) {this.two = two;}
  public MyOtherClass getThree() {return three;}
  void setThree(MyOtherClass three) {this.three = three;}
}

class MyOtherClass
{
  String four;
  String[] five;

  public String getFour() {return four;}
  void setFour(String four) {this.four = four;}
  public String[] getFive() {return five;}
  void setFive(String[] five) {this.five = five;}
}

Salida:

{
  "one" : "AAA",
  "two" : [ "BBB", "CCC" ],
  "three" : {
    "four" : "DDD",
    "five" : [ "EEE", "FFF" ]
  }
}

Si este enfoque no se ajusta exactamente a sus necesidades, si busca la API docs v1.8.1 para "pretty", mostrará los componentes relevantes disponibles. Si utiliza la versión 2 de la API.x a continuación, mire en su lugar la API 2.1.0 más reciente docs .

 22
Author: Programmer Bruce,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-12-06 20:50:30

Podría sugerir este enfoque, es válido con Spring 4.0.x y posiblemente versiones anteriores.

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
@EnableWebMvc    
public class WebMvcConfig extends WebMvcConfigurerAdapter {


    @Bean
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
        MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
        mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper());
        return mappingJackson2HttpMessageConverter;
    }

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper objMapper = new ObjectMapper();
        objMapper.enable(SerializationFeature.INDENT_OUTPUT);
        return objMapper;
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        super.configureMessageConverters(converters);        
        converters.add(mappingJackson2HttpMessageConverter());
    }

}

Gracias a Willie Wheeler por la solución: El blog de primavera de Willie Wheeler

 22
Author: MattJ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-08 20:47:47

Pretty print se activará agregando y configurando el convertidor MappingJackson2HttpMessageConverter. Deshabilite prettyprint dentro del entorno de producción.

Configuración del convertidor de mensajes

<mvc:annotation-driven>
    <mvc:message-converters>
        <bean id="jacksonHttpMessageConverter"
            class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <property name="prettyPrint" value="${json.prettyPrint}" />
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>
 6
Author: Ben Asmussen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-30 11:02:20

Basado en baeldung esto podría ser una buena idea usando java 8:

@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {

    Optional<HttpMessageConverter<?>> converterFound;
       converterFound = converters.stream().filter(c -> c instanceof AbstractJackson2HttpMessageConverter).findFirst();

    if (converterFound.isPresent()) {
        final AbstractJackson2HttpMessageConverter converter;
        converter = (AbstractJackson2HttpMessageConverter) converterFound.get();
        converter.getObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
        converter.getObjectMapper().enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
    }
}
 4
Author: davidwillianx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-12 21:08:42

Tuve problemas para que el MappingJacksonHttpMessageConverter personalizado funcionara como se sugirió anteriormente, pero finalmente pude hacerlo funcionar después de tener problemas con la configuración. Desde el punto de vista del código hice exactamente lo que se mencionó anteriormente, pero tuve que agregar la siguiente configuración a mi springapp-servlet.xml para que funcione.

Espero que esto ayude a otros que buscan implementar lo mismo.

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="messageConverters">
        <list>
            <ref bean="jsonConverter" />
        </list>
    </property>
</bean>

<bean id="jsonConverter" class="com.xxx.xxx.xxx.common.PrettyPrintMappingJacksonHttpMessageConverter">
    <property name="supportedMediaTypes" value="application/json" />
    <property name="prettyPrint" value="true" />
</bean>
 3
Author: user977505,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-22 07:43:43

Jackson 2 tiene una API más agradable, de acuerdo, pero no resolverá este problema en un entorno Spring MVC dado que Spring MVC usa ObjectMapper#writeValue(JsonGenerator, Object) para escribir objetos como JSON. Esta variante writeValue no aplica características de serialización ObjectMapper como INDENT_OUTPUT en Jackson 1.x ó 2.0.

Creo que esto es algo confuso. Dado que usamos el ObjectMapper para construir JsonGenerators, esperaría que los generadores devueltos se inicialicen en función de configuración de ObjectMapper configurada. Informé esto como un problema contra Jackson 2.0 aquí: https://github.com/FasterXML/jackson-databind/issues/12 .

La sugerencia de Les de llamar a JsonGenerator#useDefaultPrettyPrinter basado en el valor de una bandera prettyPrint es lo mejor que podemos hacer en este momento. He seguido adelante y creado un HttpMessageConverter Jackson2 que hace esto basado en el estado habilitado de la SerializationFeature INDENT_OUTPUT: https://gist.github.com/2423129 .

 3
Author: kdonald,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-19 19:13:01

Lo haría un problema de renderizado, no la preocupación del servicio REST.

¿Quién está haciendo el renderizado? Deje que ese componente formatee el JSON. Tal vez pueden ser dos URLs - una para producción y otra para desarrollo.

 1
Author: duffymo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-06-30 22:23:34