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.
Related
my code
#GetMapping(value = {"/metadata"}, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.OK)
#ResponseBody
public String getMetadata() {
return dppService.getMetadata();
}
the method getMetadata will just return a json string. it just read data from the json file, and it is in another library can not be changed.
But when call this api, i got the follow reponse:
"{\"Namespace\":\"com.xxx\"...
the json string was escaped.
expected:
"{"Namespace":"com.xxx"...
How could i make it return the right json? BTW, our other services also return a json string in the controller, but their response will not be escaped which is so confused for me.
You could do this two ways:
From what I could understand you are having this issues because you might be returning the json as a string from from the service method dppService.getMetadata() by converting it manually to a string. If so , change that and instead return a POJO class from the service method as well as the controller, spring default jackson converter should automatically convert it to a json when the request is served. (I would suggest you go with this approach)
Another approach (the hacky less desirable one) if you still want to keep returning a string then you could configure the StringMessageConverter like below to accept json:
#Override
public void configureMessageConverters(
List<HttpMessageConverter<?>> converters) {
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(
Charset.forName("UTF-8"));
stringConverter.setSupportedMediaTypes(Arrays.asList( //
MediaType.TEXT_PLAIN, //
MediaType.TEXT_HTML, //
MediaType.APPLICATION_JSON));
converters.add(stringConverter);
}
root cause:
There is a configuration file in the project:
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MappingJackson2HttpMessageConverter(jacksonBuilder().build()));
converters.stream()
.filter(converter -> converter instanceof MappingJackson2HttpMessageConverter)
.findFirst()
.ifPresent(converter -> ((MappingJackson2HttpMessageConverter) converter).setDefaultCharset(UTF_8));
}
This configuration overrite the defualt jackson behavior. There are two ways to solve this issue:
1.Remove this configuration, then it will be the default behavior
2.Add the StringHttpMessageConverter in this configuration, see Ananthapadmanabhan's option2
I am working on a middleware-app which deserializes values received via RestTemplate as json-String from a legacy-API (so, no influence on "their" data model and thus needing some custom config for my objectmapper consuming this api), and the app itself serves a restful API with (partially enriched and composited) data based on the legacydata as json, too.
Now, my legacy-Mapping-Classes' Constructors are all sharing a common structure like this at the moment:
...
private ObjectMapper mapper;
public MyMapper() {
this.mapper = new ObjectMapper();
this.mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
this.mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
...
because I use Jackson to de-serialize the json from the legacysystem. Basically I want to refactor this redundance using Springs DI Container.
So I tried to create my own Objectmapper #Component which just extends ObjectMapper like it is stated in some answers in this thread:
Configuring ObjectMapper in Spring - lets call it FromLegacyObjectMapper - instead of initializing my mapper in every class, so I created one and used
#Autowired
private FromLegacyObjectMapper
(or the constructorinjection-equivalent, but for simplicitys sake..).
But this had some serious sideeffects. Actually, I wasn't able to deserialize clientjson to viewmodels in my controllers anymore because of the rootvalue-wrapping, because the autowiring overwrites the spring boot standard objectmapper which I actually need when deserializing viewModels from my frontend.
I try to get it up and running like this:
frontend <---> My Api using Standard ObjectMapper <--> viewModel created by consuming legacy-Api-json using FromLegacyObjectMapper
So, what I surely could do is using a baseclass for my mappingclasses and just add the code above to the base constructor, and let every Mapperclass extend this base, but actually I hoped to find a way to use springs dependency injection container instead. I am out of ideas for now, so I hope anyone could help me out!
edit: To make it perhaps a bit clearer please see Moritz' answer below and our discussion in the comments. I am well aware I am able to use #Qualifier annotation, but this would just solve the problem if there is a way to add the #Qualifier to the standard objectmapper used in spring controllers. I'll do some research myself, but other answers are highly welcome.
I would try adding two different ObjectMappers to the Spring container. You could add something like this, for example to your Application class (assuming that is the one annotated with #SpringBootApplication):
#Bean
#Qualifier("fromLegacy")
public ObjectMapper fromLegacyObjectMapper() {
// create and customize your "from legacy" ObjectMapper here
return objectMapper;
}
#Bean
#Qualifier("default")
public ObjectMapper defaultObjectMapper() {
// create your default ObjectMapper here
return objectMapper;
}
Then you can inject the "from legacy" ObjectMapper in classes that use the legacy API like this:
public class SomeServiceUsingLegacyApi {
private final ObjectMapper objectMapper;
#Autowired
public SomeServiceUsingLegacyApi(#Qualifier("fromLegacy") ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
// [...]
}
And in other classes, using the other API accordingly:
public class SomeServiceUsingOtherApi {
private final ObjectMapper objectMapper;
#Autowired
public SomeServiceUsingOtherApi(#Qualifier("default") ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
// [...]
}
I've been reading a lot about JSONP support with Spring 4, but I still lack a clean explanation to make it work with the right media-type (under chrome)
1) I added the JsonpAdvice cfr Jackson JSONP Support
#ControllerAdvice
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {
public JsonpAdvice() {
super("callback");
}
}
2) My controller is wrapping the response with MappingJacksonValue*
#RequestMapping(value = '/api/test', method = RequestMethod.GET)
#ResponseBody
public Object test(HttpServletRequest request) {
List<String> result = new ArrayList<String>();
result.add("hello");
result.add("world");
if(request.getParameter('callback')){
MappingJacksonValue value = new MappingJacksonValue(result)
value.setJsonpFunction(request.getParameter('callback'))
return value
}
return result
}
not sure the MappingJacksonValue is necessary or if MappingJackson2HttpMessageConverter will take care of that?
3) I added explicit media-types in application.yml:
spring:
profiles.active: development
jackson.property-naming-strategy: SNAKE_CASE
mvc:
media-types:
json: 'application/json'
xml: 'application/xml'
jsonp: 'application/javascript'
However I still get the following error in Chrome:
Refused to execute script from 'https://example.com/api/test?callback=jQuery22406993800323428312_1481922214995&_=1481922214996'
because its MIME type ('application/json') is not executable, and strict MIME type checking is enabled.
Any step missing? or too much configuration?
After debugging my JsonpAdvice.groovy, I found out that AbstractJsonpResponseBodyAdvice is expecting a list of String: private final String[] jsonpQueryParamNames;
My initial code was using a simple String. Here is the fix:
#ControllerAdvice
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {
public JsonpAdvice() {
super(["callback"])
}
}
Hi I have a requirement to dynamically ignore entity fields in spring data rest response [I know they can be done in a static way by using #JsonIgnore annotation] ideally based on a spring security Role .The role part is still manageable but how to dynamically ignore fields in the json response is a challenge.
After some analysis and the docs I think jackson is the way to go as spring data rest does provide jackson customization via jackson modules and mixins http://docs.spring.io/spring-data/rest/docs/current/reference/html/#customizing-sdr.custom-jackson-deserialization .
So I think in jackson api it could be done via #jsonFilter and then suppling the same when the ObjectMapper write the object [more details here http://www.cowtowncoder.com/blog/archives/2011/09/entry_461.html] .
But I am not sure how this could be wired up with Spring data rest (basically the part where I acan inject the filterprovider into spring data rest objectmapper).Let me know if anyone has tried this or someone from the Spring data team has insights .
Will post an answer myself If I am able to achieve the same.
UPDATE
So I figured out that the way to implement custom filtering is through the jackson BeanSerializerModifier .Got great help from #cowtowncoder on twitter .Also helpful reference or holy grails for filtering with jackson http://www.cowtowncoder.com/blog/archives/2011/02/entry_443.html
So yes finally I was able to solve this .The trick here is to use a custom BeanSerializerModifier and register it via a Custom Module [which is the custom hook available to customize spring data rest jackson serialization],something like
setSerializerModifier( new CustomSerializerModifier()).build()));
now you can customize our BeanSerializerModifier by overriding the method changeProperties to apply your custom filter ,which basically includes and excludes BeanPropertyWriter based on your logic .sample below
List<BeanPropertyWriter> included = Lists.newArrayList();
for (BeanPropertyWriter property : beanProperties)
if (!filter.contains(property.getName()))
included.add(property);
this way you can include any logic per class or otherwise and filter properties form response in a custom manner.Hope It Helps
Also have updated my code on github do look at https://github.com/gauravbrills/SpringPlayground
This example shows how to implement a dynamic JSON transformation (filtering) in a Spring Boot REST controller. It is using AOP controller advice to change controller method output in runtime. Code on github: https://github.com/andreygrigoriev/jsonfilter
AOP Advice
#ControllerAdvice
#SuppressWarnings("unused")
public class FilterAdvice implements ResponseBodyAdvice<Object> {
#Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
String fields = ((ServletServerHttpRequest) request).getServletRequest().getParameter("fields");
return new FilterMappingJacksonValue<>(body, StringUtils.isEmpty(fields) ? new String[] {} : fields.split(","));
}
#Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
}
FilterMappingJacksonValue
public class FilterMappingJacksonValue<T> extends MappingJacksonValue {
public FilterMappingJacksonValue(final T value, final String... filters) {
super(value);
setFilters(new SimpleFilterProvider().addFilter("dynamicFilter",
filters.length > 0 ? SimpleBeanPropertyFilter.filterOutAllExcept(filters) : SimpleBeanPropertyFilter.serializeAll()));
}
}
Simple DTO
#Data
#AllArgsConstructor
#JsonFilter("dynamicFilter")
public class Book {
String name;
String author;
}
BookController
#RestController
#SuppressWarnings("unused")
public class BookController {
#GetMapping("/books")
public List<Book> books() {
List<Book> books = new ArrayList<>();
books.add(new Book("Don Quixote", "Miguel de Cervantes"));
books.add(new Book("One Hundred Years of Solitude", "Gabriel Garcia Marquez"));
return books;
}
}
I'm using Jackson 1.9.6 (codehaus) for JSON serialization of my response bodies in a Spring MVC application, and I'm having trouble finding a way to configure pretty printing. All of the code examples I've been able to find (like this and this) involve playing with an instantiation of ObjectMapper or ObjectWriter, but I don't currently use an instantiation of these for anything else. I wouldn't even know where to put this code. All of my Jackson configurations are taken care of by annotating the POJOs being serialized to JSON.
Is there a way to specify pretty printing in an annotation? I would think they would have put that in #JsonSerialize, but it doesn't look like it.
My class to be serialized looks like this:
#JsonAutoDetect
#JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
public class JSONObject implements Serializable{...}
and my Spring controller method looks like this:
#RequestMapping(method = RequestMethod.GET)
public #ResponseBody List<Object> getMessagesAndUpdates(HttpServletRequest request, HttpServletResponse response) {
JSONObject jsonResponse = new JSONObject();
.
.
.
//this will generate a non-pretty-printed json response. I want it to be pretty-printed.
return jsonResponse;
}
I searched and searched for something similar and the closest I could find was adding this bean to my Application context configuration (NOTE: I am using Spring Boot so I am not 100% certain this will work as-is in a non-Spring Boot app):
#Bean
public Jackson2ObjectMapperBuilder jacksonBuilder()
{
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.indentOutput(true);
return builder;
}
In my opinion, its the cleanest available solution and works pretty well.
Adding this as a separate answer so I can format the output.
As luck would have it, the non-Spring Boot solution wasn't too far from the Spring Boot solution :)
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.indentOutput(true).dateFormat(new SimpleDateFormat("yyyy-MM-dd"));
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()));
}