I am using #Responsebody String to send JSON.
part of my code is below.(I am sorry for open it all)
#RequestMapping(value = "/getSomeList", method = RequestMethod.GET ,
headers="Accept=application/json", produces = "text/plain;charset=UTF-8")
public #ResponseBody String getThumbList(
#RequestParam("con_id") String con_id) throws Exception{
return json;
}
And actually it sends Json. But my client requests Bson type. How can I change Json to Bson without editing global format(my json is actually just string and I heard that spring can not response bson. Is that right?).
You need to register a HttpMessageConverter that can convert to BSON.
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="MyBsonHttpMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>
Unfortunaly Spring has no Bson Http Message Converter, so you have to implement your own. (Or have more luck then me while google for one).
This is an old question, but it is nigh the only thing I can find on the web on this topic.
So here's how to do it. Assuming you're using Jackson as your JSON library in Spring.
Add a dependency to Michel Krämer's bson4jackson library
Create a class like so:
import com.fasterxml.jackson.databind.ObjectMapper;
import de.undercouch.bson4jackson.BsonFactory;
import de.undercouch.bson4jackson.BsonGenerator;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import javax.annotation.Nonnull;
public class MappingJackson2BsonHttpMessageConverter
extends AbstractJackson2HttpMessageConverter
{
public MappingJackson2BsonHttpMessageConverter(#Nonnull Jackson2ObjectMapperBuilder builder) {
super(bsonObjectMapper(builder), MediaType.parseMediaType("application/bson"));
}
#Nonnull
private static ObjectMapper bsonObjectMapper(#Nonnull Jackson2ObjectMapperBuilder om){
BsonFactory f = new BsonFactory();
f.configure(BsonGenerator.Feature.ENABLE_STREAMING, true);
return om.factory(f).build();
}
}
Add it as a #Bean to your configuration, or annotate it with #Component and make sure it's in your #ComponentScan's path.
That's it. Now, if you declare your MVC endpoint with #RequestMapping(produces = "application/bson") (in some form or another), the output will be the BSON encoding of your ResponseEntity's body.
This works with Jackson-annotated Objects which you'd normally serialize to JSON; and all the configuration you did to the Jackson ObjectMapper through Spring, modules or annotations will apply.
It also works for input as well as for output.
It also works with Feign clients.
I assume it also works with Spring's RestTemplate.
Related
So I had a perfectly working Spring app. Most of my controller methods are for ajax calls that return JSON via #ResponseBody with the jackson api and returns my Java POJO to JSON.
I have a need to turn XML to JSON, so I find that Jackson has a tool for that, and I add this to my POM to use the library:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.0</version>
</dependency>
So that I may use this:
XmlMapper xmlMapper = new XmlMapper();
JsonNode node = xmlMapper.readTree(sb.toString().getBytes());
But now the #ResponseBody is returning XML and not JSON. I Remove the dependency and the controllers return JSON again.
Any way to get both? I want the xmlMapper, and JSON from the response body.
jackson-dataformat-xml appears to be registering a MappingJackson2HttpMessageConverter with a XmlMapper, along with other HttpMessageConverters that work with XML. If you always intended to return JSON from your controllers, you can change what HttpMessageConverter your app uses by overriding configureMessageConverters
For Spring 5.0 and above,
#Configuration
public class HttpResponseConfig implements WebMvcConfigurer {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.removeIf(converter -> supportsXml(converter) || hasXmlMapper(converter));
}
private boolean supportsXml(HttpMessageConverter<?> converter) {
return converter.getSupportedMediaTypes().stream()
.map(MimeType::getSubtype)
.anyMatch(subType -> subType.equalsIgnoreCase("xml"));
}
private boolean hasXmlMapper(HttpMessageConverter<?> converter) {
return converter instanceof MappingJackson2HttpMessageConverter
&& ((MappingJackson2HttpMessageConverter)converter).getObjectMapper().getClass().equals(XmlMapper.class);
}
}
For older versions of Spring, replace implements WebMvcConfigurer with extends WebMvcConfigurerAdapter
Add Accept: application/json to HTTP request header.
Read this for an analysis of how Spring does content negotiation and allows producing either XML or JSON.
The simplest way is to add an extension at the URL: Instead of /path/resource use /path/resource.json
You may also add a format parameter e.g. /path/resource?format=json or pass an appropriate Accept header
In my case, the XmlMapper was actually inserted into the application context as an #Bean. The other solutions here did not work for me. It seems like one of those issues where context matters, so for people coming here from a different context than the other answerers, here's another angle: I had to insert my own ObjectMapper.
#Configuration
public class XmlMapperConfiguration {
#Bean // me, culprit
public XmlMapper xmlMapper() {
return new XmlMapper();
}
#Bean // to make sure the rest of the application still works with JSON
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
}
I'm going to throw in an #Primary on the ObjectMapper one. It seems suspicious that Spring would even choose that one consistently. Since XmlMapper extends ObjectMapper, why would it not take that one, so #Primary won't hurt.
I have a jersey webservice running 1.17 and supports returning responses via both XML and JSON via the #Produces annotation. I am assuming it uses JAXB by default when returning JSON responses but I have no way to confirm it. As of now, my existing clients also use the same JAXB serializer/deserializer. I want to create a new client that uses Jackson without impacting the existing clients.
The JAXB JSON response is incompatible for Jackson for Maps. the JSON for a map using JAXB is of the form
"mapName":{"entry":[{"key":"key1","value":"value1"},{"key":"key2","value":"value2"}]}
and Jackson fails to parse this. Is there any way to make jackson parse this JSON?
Another Attempt: Switching Jersey to use Jackson
This isn't the preferred option but I tried setting "com.sun.jersey.api.json.POJOMappingFeature" to true to allow it to use Jackson for JSON Serialization/Deserialization however the service ends up returning 500s on response without logging any exceptions. the log4j logger level is set to TRACE. I enabled the ContainerRepsonseFilter to confirm 500s in the response and to my surprise, it logs the successful 2xx response. My guess is the problem occurs somewhere further down the stack but I don't know where.
I ended up with using MOXy which is able to parse the above json format.
#Provider
public class JsonMoxyConfigurationContextResolver implements ContextResolver {
private final MoxyJsonConfig config;
public JsonMoxyConfigurationContextResolver() {
final Map<String, String> namespacePrefixMapper = new HashMap<String, String>();
namespacePrefixMapper.put("http://www.w3.org/2001/XMLSchema-instance", "xsi");
config = new MoxyJsonConfig()
.setNamespacePrefixMapper(namespacePrefixMapper)
.setNamespaceSeparator(':');
}
#Override
public MoxyJsonConfig getContext(Class<?> objectType) {
return config;
}
}
and enabled it Jersey 2.x client using
cc.register(JsonMoxyConfigurationContextResolver.class);
I am trying to serialize a JAXB generated class. With Jettison, I am able to create a hash-map mapping the XML namespaces to any JSON prefix. With Jettison, I also get the case sensitivity right in the serialization. With JACKSON, it is all lower case. So, it seems that Jettison is able to understand XMLRootElement(name=…) much better.
How do I make JACKSON better understand JAXB annotations like XMLRootElement?
How do I provide JACKSON with a XML→JSON namespace mapper?
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
EclipseLink MOXy is a JAXB (JSR-222) compliant implementation. In EclipseLink 2.4.0 we introduced JSON-binding. Since MOXy is a JAXB implementation you will find the JSON output MOXy produces will be very consistent with XML output based on the same metadata. I will demonstrate below with an example.
DOMAIN MODEL
Below is the domain model I will use for this answer. For more information on specifying namespace information in a JAXB model see: http://blog.bdoughan.com/2010/08/jaxb-namespaces.html
package-info
#XmlSchema(
namespace="http://www.example.com/A",
elementFormDefault=XmlNsForm.QUALIFIED,
xmlns={
#XmlNs(prefix="a",namespaceURI = "http://www.example.com/A"),
#XmlNs(prefix="b",namespaceURI = "http://www.example.com/B")
}
)
package forum13214306;
import javax.xml.bind.annotation.*;
Customer
package forum13214306;
import javax.xml.bind.annotation.*;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
String firstName;
#XmlElement(namespace="http://www.example.com/B")
String lastName;
}
XML HANDLING
Below is an example of how the domain model corresponds to an XML representation.
Demo
package forum13214306;
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Customer.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum13214306/input.xml");
Customer customer = (Customer) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(customer, System.out);
}
}
input.xml/Output
<?xml version="1.0" encoding="UTF-8"?>
<a:customer xmlns:b="http://www.example.com/B" xmlns:a="http://www.example.com/A">
<a:firstName>Jane</a:firstName>
<b:lastName>Doe</b:lastName>
</a:customer>
JSON HANDLING - WITHOUT NAMESPACES
Namespaces aren't a JSON concept so I would recommend not simulating them if you can avoid this. Below I'll demonstrate that MOXy doesn't need them. Note the exact same domain model and JAXBContext is used here that was used for the XML document with namespaces.
jaxb.properties
To specify MOXy as your JSON provider you need to include a file called jaxb.properties in the same package as your domain model with the following entry (see: http://blog.bdoughan.com/search/label/jaxb.properties).
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Demo
To enable JSON binding the MEDIA_TYPE property needs to be enable on the Marshaller and Unmarshaller.
package forum13214306;
import java.io.File;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.MarshallerProperties;
import org.eclipse.persistence.jaxb.UnmarshallerProperties;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Customer.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
unmarshaller.setProperty(UnmarshallerProperties.MEDIA_TYPE, "application/json");
File json = new File("src/forum13214306/input.json");
Customer customer = (Customer) unmarshaller.unmarshal(json);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty(MarshallerProperties.MEDIA_TYPE, "application/json");
marshaller.marshal(customer, System.out);
}
}
input.json/Output
Below is the input to and output from running the demo code. Note how there is no simulated namespace information in the JSON document.
{
"customer" : {
"firstName" : "Jane",
"lastName" : "Doe"
}
}
JSON HANDLING - WITH SIMULATED NAMESPACES
Demo
If you really want to simulate namespaces in your JSON document you can leverage the NAMESPACE_PREFIX_MAPPER property on the Marshaller and Unmarshaller to do so.
package forum13214306;
import java.io.File;
import java.util.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.MarshallerProperties;
import org.eclipse.persistence.jaxb.UnmarshallerProperties;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Customer.class);
Map<String, String> namespaceToPrefixMap = new HashMap<String, String>(2);
namespaceToPrefixMap.put("http://www.example.com/A", "a");
namespaceToPrefixMap.put("http://www.example.com/B", "b");
Unmarshaller unmarshaller = jc.createUnmarshaller();
unmarshaller.setProperty(UnmarshallerProperties.MEDIA_TYPE, "application/json");
unmarshaller.setProperty(UnmarshallerProperties.JSON_NAMESPACE_PREFIX_MAPPER, namespaceToPrefixMap);
File json = new File("src/forum13214306/input.json");
Customer customer = (Customer) unmarshaller.unmarshal(json);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty(MarshallerProperties.MEDIA_TYPE, "application/json");
marshaller.setProperty(MarshallerProperties.NAMESPACE_PREFIX_MAPPER, namespaceToPrefixMap);
marshaller.marshal(customer, System.out);
}
}
input.json/Output
{
"a.customer" : {
"a.firstName" : "Jane",
"b.lastName" : "Doe"
}
}
FOR MORE INFORMATION
http://blog.bdoughan.com/2011/08/json-binding-with-eclipselink-moxy.html
http://blog.bdoughan.com/2012/05/moxy-as-your-jax-rs-json-provider.html
JSON does not have namespaces, unlike XML. So why do you feel you need namespace mapping? Databinding means mapping between Java POJOs and data format, using features of the format. For XML this includes namespaces, choice of elements vs attributes and so on. With JSON much of this complexity is removed, meaning that property names are used as is.
As to JAXB annotations: Jackson has its own set of annotations that are better match, but if you do want to use JAXB annotations as additional or alternative configuration source, you will need to use JAXB Annotation module.
But as to using XMLRootElement, it is unnecessary for JSON: JSON Objects do not have name.
I do not know what you mean by "get the case sensitivity right in the serialization" -- right in what sense? What is the problem? You will need to give an example of POJO definition as well as expected JSON.
Finally, keep in mind that there is no need for JSON and XML notations to look like each other. These are different data formats with different logical data models, and naturally differing mappings: JSON has native distinction between Arrays and Objects, for example; whereas XML has to use Elements for both. And XML has namespaces, attributes, that JSON lacks. These lead to different natural mappings, and attempts to "unify" the two lead to somewhat unnatural results for one or the other, or both. In case of Jettison (and frameworks that use it), it's ugly JSON ("franken-JSON").
I have a problem. Could you please help me.
I have an application: CXF+Spring+JAXB+REST. And I try to generate Response using JSONProvider class
There is a bean class:
#XmlRootElement
class Foo {
#XmlElement
private String bar;
}
When I set to the field value:
setBar("true")
or
setBar("false");
JSONProvider returns to me:
"bar":false
But, I expect
"bar":"false"
Because I use String type. What should I do with that?
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
The JAXB (JSR-222) specification does not cover JSON-binding. In a REST/JAX-RS context it is up to the provider to apply the JAXB mapping rules to a JSON representation. There are 3 different approaches that are being used today:
Use a JAXB implementation with a library like Jettison. Jettison implements the StAX interfaces but interprets JSON. The advantage of this approach is that it can be used with any JAXB implementation to produce/consume JSON, the disadvantage of this approach is that Jettison does not have enough information to do a perfect JSON representation. Jettison receives the String "true" without knowing what the corresponding Java type was, so it decides to represent it as a JSON boolean, since it is probably the desired output most of the time.
REST/JAX-RS implementation leverages a specific JAXB implementation to produce JSON. This approach is similar to the first, except that the REST/JAX-RS provider makes proprietary calls to the JAXB provider to make more educated guesses to what the JSON representation should be.
The JAXB implementation provides the JSON-binding implementation. Currently EclipseLink MOXy is the only JAXB implementation to support JSON-binding.
MOXy EXAMPLE
Root
Below is a domain object with String and boolean fields. I've also added fields annotated with #XmlSchemaType which can be used to override the default representation.
package forum11145933;
import javax.xml.bind.annotation.*;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Root {
String barString;
boolean barBoolean;
#XmlSchemaType(name="boolean")
String barStringWithXmlTypeBoolean;
#XmlSchemaType(name="string")
boolean barBooleanWithXmlTypeString;
}
jaxb.properties
To specify MOXy as your JAXB provider you need to add a file called jaxb.properties in the same package as your domain model with the following entry:
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Demo
The following code demonstrates how to marshal the domain object to JSON. Note that there are no compile time dependencies on MOXy.
package forum11145933;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Root.class);
Root root = new Root();
root.barString = "true";
root.barBoolean = true;
root.barStringWithXmlTypeBoolean = "true";
root.barBooleanWithXmlTypeString = true;
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty("eclipselink.media-type", "application/json");
marshaller.marshal(root, System.out);
}
}
Output
The following is the output from running the Demo code. Note how the boolean and String properties are written out correctly. Also notice how the use of the #XmlSchemaType annotation allows us to marshal a boolean as a String and vice versa.
{
"root" : {
"barString" : "true",
"barBoolean" : true,
"barStringWithXmlTypeBoolean" : true,
"barBooleanWithXmlTypeString" : "true"
}
}
MOXy & JAX-RS
MOXy includes MOXyJsonProvider which can be used to enable MOXy as the JSON provider in your JAX-RS environment:
http://blog.bdoughan.com/2012/05/moxy-as-your-jax-rs-json-provider.html
While developing REST services using Spring MVC, I would like render JSON 'pretty printed' in development but normal (reduced whitespace) in production.
If you are using Spring Boot 1.2 or later the simple solution is to add
spring.jackson.serialization.INDENT_OUTPUT=true
to the application.properties file. This assumes that you are using Jackson for serialization.
If you are using an earlier version of Spring Boot then you can add
http.mappers.json-pretty-print=true
This solution still works with Spring Boot 1.2 but it is deprecated and will eventually be removed entirely. You will get a deprecation warning in the log at startup time.
(tested using spring-boot-starter-web)
I had an answer when I posted this question, but I thought I'd post it anyway in case there are better alternative solutions. Here was my experience:
First thing's first. The MappingJacksonHttpMessageConverter expects you to inject a Jackson ObjectMapper instance and perform Jackson configuration on that instance (and not through a Spring class).
I thought it would be as easy as doing this:
Create an ObjectMapperFactoryBean implementation that allows me to customize the ObjectMapper instance that can be injected into the MappingJacksonHttpMessageConverter. For example:
<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>
And then, in my ObjectMapperFactoryBean implementation, I could do this (as has been documented as a solution elsewhere on SO):
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, isPrettyPrint());
return mapper;
But it didn't work. And trying to figure out why is a nightmare. It is a major test of patience to figure Jackson out. Looking at its source code only confuses you further as it uses outdated and obtuse forms of configuration (integer bitmasks for turning on/off features? Are you kidding me?)
I essentially had to re-write Spring's MappingJacksonHttpMessageConverter from scratch, and override its writeInternal implementation to be the following:
#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);
}
}
The only thing I added to the existing implementation is the following block:
if (isPrettyPrint()) {
jsonGenerator.useDefaultPrettyPrinter();
}
isPrettyPrint() is just a JavaBeans compatible getter w/ matching setter that I added to my MappingJacksonHttpMessageConverter subclass.
Only after jumping through these hoops was I able to turn on or off pretty printing based on my ${json.prettyPrint} value (that is set as a property depending on how the app is deployed).
I hope this helps someone out in the future!
When you are using Jackson 2.0.0, you can do it in a way Les wanted to.
I currently use RC3 and the configuration seems to be working as expected.
ObjectMapper jacksonMapper = new ObjectMapper();
jacksonMapper.configure(SerializationFeature.INDENT_OUTPUT, true);
translates
{"foo":"foo","bar":{"field1":"field1","field2":"field2"}}
into
{
"foo" : "foo",
"bar" : {
"field1" : "field1",
"field2" : "field2"
}
}
Might I suggest this approach, it is valid with Spring 4.0.x and possibly older versions.
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());
}
}
Thanks to Willie Wheeler for the solution: Willie Wheeler's Spring blog
How do I make Jackson pretty-print the JSON content it generates?
Here's a simple example:
Original JSON Input:
{"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;}
}
Output:
{
"one" : "AAA",
"two" : [ "BBB", "CCC" ],
"three" : {
"four" : "DDD",
"five" : [ "EEE", "FFF" ]
}
}
If this approach doesn't exactly fit your needs, if you search the API docs v1.8.1 for "pretty", it'll turn up the relevant components available. If you use API version 2.x then look instead at the newer API 2.1.0 docs.
Pretty print will be enable by adding and configure the MappingJackson2HttpMessageConverter converter. Disable prettyprint within production environment.
Message converter configuration
<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>
Based on baeldung this could be a nice idea using 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);
}
}
I had trouble getting the custom MappingJacksonHttpMessageConverter to work as suggested above but I was finally able to get it to work after struggling w/ the configuration. From the code stand point I did exactly what was mentioned above but I had to add the following configuration to my springapp-servlet.xml to get it to work.
I hope this helps others who are looking to implement the same.
<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>
Jackson 2 has a nicer API, agreed, but it won't resolve this problem in a Spring MVC environment given Spring MVC uses ObjectMapper#writeValue(JsonGenerator, Object) to write objects out as JSON. This writeValue variant does not apply ObjectMapper serialization features such as INDENT_OUTPUT in either Jackson 1.x or 2.0.
I do think this is somewhat confusing. Since we use the ObjectMapper to construct JsonGenerators, I'd expect returned generators to be initialized based on configured ObjectMapper settings. I reported this as a issue against Jackson 2.0 here: https://github.com/FasterXML/jackson-databind/issues/12.
Les's suggestion of calling JsonGenerator#useDefaultPrettyPrinter based on the value of a prettyPrint flag is about the best we can do at the moment. I've gone ahead and created a Jackson2 HttpMessageConverter that does this based on the enabled status of the INDENT_OUTPUT SerializationFeature: https://gist.github.com/2423129.
I would make that a rendering issue, not the concern of the REST service.
Who's doing the rendering? Let that component format the JSON. Maybe it can be two URLs - one for production and another for development.