Подтвердить что ты не робот

При использовании Spring MVC для REST, как вы разрешаете Джексону красиво печатать визуализированный JSON?

При разработке служб REST, использующих Spring MVC, я хотел бы сделать JSON "довольно напечатанным" в разработке, но нормальным (сокращенным пробелом) в процессе производства.

4b9b3361

Ответ 1

У меня был ответ, когда я разместил этот вопрос, но я думал, что я опубликую его в любом случае, если будут альтернативные решения. Вот мой опыт:

Прежде всего. MappingJacksonHttpMessageConverter ожидает, что вы введете экземпляр Jackson ObjectMapper и выполните настройку Jackson на этом экземпляре (а не через класс Spring).

Я думал, что это будет так же просто, как это сделать:

Создайте реализацию ObjectMapperFactoryBean, которая позволяет мне настроить экземпляр ObjectMapper, который можно ввести в MappingJacksonHttpMessageConverter. Например:

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

И затем, в моей реализации ObjectMapperFactoryBean, я мог бы сделать это (как было описано в качестве решения в другом месте на SO):

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

Но это не сработало. И пытаюсь понять, почему это кошмар. Это серьезный тест на терпение, чтобы вывести Джексона. Взгляд на его исходный код только смущает вас, поскольку он использует устаревшие и тупые формы конфигурации (целые битмаски для включения/выключения функций? Вы издеваетесь?)

Мне по существу пришлось перезаписать Spring MappingJacksonHttpMessageConverter с нуля и переопределить его реализацию writeInternal следующим образом:

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

Единственное, что я добавил к существующей реализации, - это следующий блок:

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

isPrettyPrint() - это просто совместимый с JavaBeans getter w/match setter, который я добавил в мой подкласс MappingJacksonHttpMessageConverter.

Только после перескакивания этих обручей я смог включить или отключить красивую печать на основе моего значения ${json.prettyPrint} (которое устанавливается как свойство в зависимости от того, как приложение развертывается).

Я надеюсь, что это поможет кому-то в будущем!

Ответ 2

Если вы используете Spring Boot 1.2 или более поздней версии, простым решением является добавление

spring.jackson.serialization.INDENT_OUTPUT=true

в файл application.properties. Это предполагает, что вы используете Jackson для сериализации.

Если вы используете более раннюю версию Spring Boot, вы можете добавить

http.mappers.json-pretty-print=true

Это решение по-прежнему работает с Spring Boot 1.2, но оно устарело и в конечном итоге будет полностью удалено. Во время запуска вы получите предупреждение об устаревании.

(проверено с помощью spring-boot-starter-web)

Ответ 3

Когда вы используете Jackson 2.0.0, вы можете сделать это так, как хотел Les. В настоящее время я использую RC3, и конфигурация работает как ожидалось.

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

переводит

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

в

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

Ответ 4

Как сделать "Джексон" достаточно печатным для него создаваемым JSON-контентом?

Вот простой пример:

Исходный вход JSON:

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

Вывод:

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

Если этот подход не соответствует вашим потребностям, если вы ищете API docs v1.8.1 для "симпатичного", это будет включите соответствующие компоненты. Если вы используете API версии 2.x, посмотрите вместо этого на новые API 2.1.0.

Ответ 5

Возможно, я предлагаю этот подход, он действителен с Spring 4.0.x и, возможно, более старыми версиями.

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

}

Благодаря Вилли Уилеру за решение: Вилли Уилер Spring блог

Ответ 6

Довольно печатная версия будет включена, добавив и настроив конвертер MappingJackson2HttpMessageConverter. Отключите отпечаток в рабочей среде.

Конфигурация конвертера сообщений

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

Ответ 7

У меня возникла проблема с тем, что пользовательский MappingJacksonHttpMessageConverter работал, как было предложено выше, но я, наконец, смог заставить его работать после борьбы с конфигурацией. С точки зрения кода я сделал именно то, что было упомянуто выше, но мне пришлось добавить следующую конфигурацию в мой springapp-servlet.xml, чтобы заставить ее работать.

Я надеюсь, что это поможет другим, которые хотят реализовать то же самое.

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

Ответ 8

У Jackson 2 есть более удобный API, но он не решит эту проблему в среде Spring MVC, указанной в Spring. MVC использует ObjectMapper # writeValue (JsonGenerator, Object) для записи объектов как JSON. Этот вариант writeValue не применяет функции сериализации ObjectMapper, такие как INDENT_OUTPUT, в Jackson 1.x или 2.0.

Я действительно думаю, что это несколько сбивает с толку. Поскольку мы используем ObjectMapper для построения JsonGenerators, я ожидаю, что возвращаемые генераторы будут инициализированы на основе настроенных параметров ObjectMapper. Я сообщил об этом как о проблеме с Jackson 2.0 здесь: https://github.com/FasterXML/jackson-databind/issues/12.

Предложение о вызове JsonGenerator # useDefaultPrettyPrinter на основе значения символа prettyPrint - это лучшее, что мы можем сделать в данный момент. Я пошел вперед и создал Jackson2 HttpMessageConverter, который делает это на основе включенного статуса INDENT_OUTPUT SerializationFeature: https://gist.github.com/2423129.

Ответ 9

На основе baeldung это может быть хорошей идеей, используя 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);
    }
}

Ответ 10

Я бы сделал проблему с рендерингом, а не заботой службы REST.

Кто делает рендеринг? Пусть этот компонент форматирует JSON. Возможно, это могут быть два URL - один для производства, а другой для разработки.