web-dev-qa-db-fra.com

Lorsque vous utilisez Spring MVC pour REST, comment pouvez-vous permettre à Jackson d'imprimer le JSON rendu de manière jolie?

Lors du développement de services REST à l'aide de Spring MVC, j'aimerais rendre JSON "assez imprimé" en développement mais normal (espace réduit) en production.

45
Les Hazlewood

J'avais une réponse lorsque j'ai posté cette question, mais j'ai pensé que je la publierais de toute façon au cas où il y aurait de meilleures solutions alternatives. Voici mon expérience:

Tout d'abord. MappingJacksonHttpMessageConverter s'attend à ce que vous injectiez une instance Jackson ObjectMapper et effectuiez la configuration Jackson sur cette instance (et non via une classe Spring).

J'ai pensé que ce serait aussi simple que de faire ça:

Créer une implémentation ObjectMapperFactoryBean qui me permet de personnaliser l'instance ObjectMapper qui peut être injectée dans MappingJacksonHttpMessageConverter. Par exemple:

<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>

Et puis, dans mon implémentation ObjectMapperFactoryBean, je pourrais le faire (comme cela a été documenté comme solution ailleurs sur SO):

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

Mais ça n'a pas marché. Et essayer de comprendre pourquoi est un cauchemar. C'est un test de patience majeur pour comprendre Jackson. Regarder son code source ne fait que vous embrouiller car il utilise des formes de configuration obsolètes et obtuses (masques de bits entiers pour activer/désactiver les fonctionnalités? Vous plaisantez?)

J'ai dû essentiellement réécrire MappingJacksonHttpMessageConverter de Spring à partir de zéro, et remplacer son implémentation writeInternal comme suit:

@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);
    }
}

La seule chose que j'ai ajoutée à l'implémentation existante est le bloc suivant:

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

isPrettyPrint() est juste un getter compatible JavaBeans avec setter correspondant que j'ai ajouté à ma sous-classe MappingJacksonHttpMessageConverter.

Ce n'est qu'après avoir sauté à travers ces cercles que j'ai pu activer ou désactiver la jolie impression en fonction de ma valeur ${json.prettyPrint} (Qui est définie comme une propriété en fonction de la façon dont l'application est déployée).

J'espère que cela aidera quelqu'un à l'avenir!

31
Les Hazlewood

Si vous utilisez Spring Boot 1.2 ou version ultérieure, la solution simple consiste à ajouter

spring.jackson.serialization.INDENT_OUTPUT=true

à la application.properties fichier. Cela suppose que vous utilisez Jackson pour la sérialisation.

Si vous utilisez une version antérieure de Spring Boot, vous pouvez ajouter

http.mappers.json-pretty-print=true

Cette solution fonctionne toujours avec Spring Boot 1.2 mais elle est obsolète et sera finalement supprimée entièrement. Vous recevrez un avertissement de dépréciation dans le journal au démarrage.

(testé à l'aide de spring-boot-starter-web)

43
user4061342

Lorsque vous utilisez Jackson 2.0.0, vous pouvez le faire de la manière voulue par Les. J'utilise actuellement RC3 et la configuration semble fonctionner comme prévu.

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

traduit

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

dans

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

Comment puis-je faire en sorte que Jackson imprime le contenu JSON qu'il génère?

Voici un exemple simple:

Entrée JSON d'origine:

{"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;}
}

Sortie:

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

Si cette approche ne correspond pas exactement à vos besoins, si vous recherchez la documentation de l'API v1.8.1 pour "jolie", elle affichera les composants pertinents disponibles. Si vous utilisez la version 2.x de l'API, regardez plutôt les documents API 2.1.0 plus récents .

22
Programmer Bruce

Puis-je suggérer cette approche, elle est valide avec Spring 4.0.x et peut-être des versions plus anciennes.

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());
    }

}

Merci à Willie Wheeler pour la solution: le blog du printemps de Willie Wheeler

22
MattJ

La jolie impression sera activée en ajoutant et en configurant le convertisseur MappingJackson2HttpMessageConverter . Désactivez prettyprint dans l'environnement de production.

Configuration du convertisseur de messages

<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
Ben Asmussen

Basé sur baeldung cela pourrait être une bonne idée en utilisant 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
davidwillianx

Jackson 2 a une meilleure API, d'accord, mais cela ne résoudra pas ce problème dans un environnement Spring MVC étant donné que Spring MVC utilise ObjectMapper # writeValue (JsonGenerator, Object) pour écrire des objets en JSON. Cette variante writeValue n'applique pas les fonctionnalités de sérialisation d'ObjectMapper telles que INDENT_OUTPUT dans Jackson 1.x ou 2.0.

Je pense que cela est quelque peu déroutant. Puisque nous utilisons l'ObjectMapper pour construire JsonGenerators, je m'attends à ce que les générateurs renvoyés soient initialisés en fonction des paramètres ObjectMapper configurés. J'ai signalé cela comme un problème contre Jackson 2.0 ici: https://github.com/FasterXML/jackson-databind/issues/12 .

La suggestion de Les d'appeler JsonGenerator # useDefaultPrettyPrinter en se basant sur la valeur d'un indicateur prettyPrint est la meilleure chose que nous puissions faire pour le moment. Je suis allé de l'avant et j'ai créé un Jackson2 HttpMessageConverter qui fait cela en fonction du statut activé de INDENT_OUTPUT SerializationFeature: https://Gist.github.com/2423129 .

3
kdonald

J'ai eu du mal à faire fonctionner le MappingJacksonHttpMessageConverter personnalisé comme suggéré ci-dessus, mais j'ai finalement réussi à le faire fonctionner après des difficultés avec la configuration. Du point de vue du code, j'ai fait exactement ce qui a été mentionné ci-dessus, mais j'ai dû ajouter la configuration suivante à mon springapp-servlet.xml pour le faire fonctionner.

J'espère que cela aide ceux qui cherchent à mettre en œuvre la même chose.

<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
user977505

Je voudrais en faire un problème de rendu, pas le souci du service REST.

Qui fait le rendu? Laissez ce composant formater le JSON. Il peut s'agir de deux URL, l'une pour la production et l'autre pour le développement.

1
duffymo