In Spring boot, is it possible to have many different versions of gson or Jackson http converters and use them dynamically whenever I need a specific type of data format?
You have to create two beans for GsonHttpMessageConverter the first one with settings by default and second one with setting for serializing nulls by following way:
#Bean
public GsonHttpMessageConverter gsonHttpMessageConverter() {
return buildGsonHttpMessageConverter(MapperUtil.getGsonInstance());
}
#Bean
public GsonHttpMessageConverter gsonHttpMessageConverterWithNulls() {
return buildGsonHttpMessageConverter(MapperUtil.getGsonInstanceSerializeNulls());
}
private GsonHttpMessageConverter buildGsonHttpMessageConverter(final Gson gson) {
final GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
converter.setGson(gson);
return converter;
}
And when you want to use one of them then call #Qualifier("someBean") annotation. by following way:
#Autowired
#Qualifier("gsonHttpMessageConverter")
GsonHttpMessageConverter gsonHttpMessageConverter;
#Autowired
#Qualifier("gsonHttpMessageConverterWithNulls")
GsonHttpMessageConverter gsonHttpMessageConverterWithNulls;
Related
I am migrating my project from springMVC to springboot. One of the controller has an API returning in this way.
I am trying to return JSONObject,
Here is my interface:
public class myController{
#RequestMapping(value = "/api", method = { RequestMethod.GET,
RequestMethod.POST })
public #ResponseBody JSONObject myfunction(HttpServletRequest request,
HttpServletResponse response);
}
I get the the following error:
No converter found for return value of type: class org.json.JSONObject
I have added jackson dependencies. I want to return JSONObject only. I have seen solutions but they are advising me to convert to a string. But i cannot do that as this could affect when i am returning to the front end(Which has been written already in jsp and jQuery and is expecting a JSONObject.)
How do i solve this?
Thanks.
You can use ObjectNode of Jackson library to keep JSONObject structure refer link. For that you have to autowire ObjectMapper in your service
public class myController{
#Autowired
private ObjectMapper jacksonObjectMapper;
#GetMapping
public ObjectNode sayJSONObject() {
ObjectNode objectNode = jacksonObjectMapper.createObjectNode();
objectNode.put("key", "value");
return objectNode;
}
}
I'm working on a old Spring Application which contains lots of legacy code which needs to survive. Right now I'm working on a more modern approach of API usage and I've stumbled into a problem.
I've Added GSON for converting dates of different formats and front-ends (see below). But this causes a problem at runtime, the #ResponseBody objects become empty.
It all works fine in MockMVC which hooks up the config, but at runtime in Tomcat 8, it has problems. As I've been googling this for quite a while it seems that it could be a problem due to Jackson trying to parse the JSON as well.
Any idea's how I can ensure only GSON is used for JSON requests? (My requests contain only Simple Pojo's with Date, String and Long objects), but can have some nested objects.
Some code snippets:
WebConfig:
#EnableWebMvc
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new RestLoggingInterceptor());
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new ExtendedGsonHttpMessageConverter());
}
}
ExtendedGsonHttpMessageConverter
public class ExtendedGsonHttpMessageConverter extends GsonHttpMessageConverter
{
private static final String[] DATE_FORMATS = new String[] {
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
"yyyy-MM-dd'T'HH:mm:ss.SSSZ",
"yyyy-MM-dd'T'HH:mmZ",
"yyyy-MM-dd'T'HH:mm:ssZ",
"yyyy-MM-dd'T'HH:mm:ss-'07:00'"
};
public ExtendedGsonHttpMessageConverter()
{
super();
super.setGson(buildGson());
}
protected static Gson buildGson() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Date.class, new DateDeserializer());
return gsonBuilder.create();
}
private static class DateDeserializer implements JsonDeserializer<Date> {
#Override
public Date deserialize(JsonElement jsonElement, Type typeOF,
JsonDeserializationContext context) throws JsonParseException {
for (String format : DATE_FORMATS) {
try {
return new SimpleDateFormat(format, Locale.GERMANY).parse(jsonElement.getAsString());
} catch (ParseException e) {
}
}
throw new JsonParseException("Unparseable date: \"" + jsonElement.getAsString()
+ "\". Supported formats: " + Arrays.toString(DATE_FORMATS));
}
}
}
The problem was using an interceptor which was logging the request.
Logging the request causes the requestBody to be null, so now I'll be using Springs AbstractRequestLoggingFilter as in the comments.
I have a question related to the Jackson configuration on my Spring boot project
As described on spring boot blog
I try to customize my Object serialization.
After added a new config bean in my config
#Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.propertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
return builder;
}
When I try to output an instance of my class User the json result is not in CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES
Class User {
private String firstName = "Joe Blow";
public String getFirstName() {
return firstName;
}
}
json output is :
{
"firstName": "Joe Blow"
}
and not
{
"first_name": "Joe Blow"
}
Maybe I need to register something in my Jersey config to activate my custom obejctMapper Config
#Configuration
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
packages("my.package);
}
}
Thanks
The general way to configure the ObjectMapper for JAX-RS/Jersey applications is use a ContextResolver. For example
#Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
private final ObjectMapper mapper;
public ObjectMapperContextResolver() {
mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(
PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES
);
}
#Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
}
It should be picked up with the package scan, or you can explicitly register it, if it's not within the package scope
public JerseyConfig() {
register(new ObjectMapperContextResolver());
// Or if there's is an injection required
// register it as a .class instead of instance
}
The ContextResolver is called during the marshalling and unmarshalling. The class/type being serialzed or deserialized into will be passed to the getContext method. So you could even use more than one mapper for different types, or even more use cases.
UPDATE
Starting from Spring Boot 1.4, you can just create an ObjectMapper Spring bean, and Spring Boot will create the ContextResolver for you, and use your ObjectMapper
// in your `#Configuration` file.
#Bean
public ObjectMapper mapper() {}
I have a JAX-RS application using JBoss AS 7.1, and I POST/GET JSON and XML objects which include Dates (java.util.Date):
#XmlRootElement
#XmlAccessorType(XmlAccessField.FIELD)
public class MyObject implements Serializable
{
#XmlSchemaType(name = "dateTime")
private Date date;
...
}
When I use #Produce("application/xml") on the get method, the objets are serialized as XML and the dates are converted into ISO-8601 strings (e.g. "2012-12-10T14:50:12.123+02:00").
However, if I use #Produce("application/json") on the get method, the dates in the JSON objects are timestamps (e.g. "1355147452530") instead of ISO-8601 strings.
How can I do to configure the JAX-RS implementation (RESTEasy) to serialize dates in JSON format as ISO-8601 strings instead of timestamps ?
Thank you for your answers.
Note: I also tried to use a custom JAX-RS provider to do the JSON serialization for Dates
#Provider
#Produces(MediaType.APPLICATION_JSON)
public class CustomJsonDateProvider implements MessageBodyWriter<Date>
{
...
}
This provider seems to be registered by RESTeasy on JBoss startup:
[org.jboss.jaxrs] Adding JAX-RS provider classes: package.CustomJsonDateProvider
...
[org.jboss.resteasy.cdi.CdiInjectorFactory] No CDI beans found for class package.CustomJsonDateProvider. Using default ConstructorInjector.
but it is never used !
I assume your json parser is Jackson, try:
#JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd,HH:00", timezone="CET")
public Date date;
(since Jackson 2.0)
The default JBoss parser is Jettison, but I wasn't able to change the date format. So I switched to Jackson and added the following class to my project to configure it:
#Provider
#Produces(MediaType.APPLICATION_JSON)
public class JacksonConfig implements ContextResolver<ObjectMapper>
{
private final ObjectMapper objectMapper;
public JacksonConfig()
{
objectMapper = new ObjectMapper();
objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESPAMPS, false);
}
#Override
public ObjectMapper getContext(Class<?> objectType)
{
return objectMapper;
}
}
Sorry people for yelling out loud - I found the answers here
http://wiki.fasterxml.com/JacksonFAQDateHandling,
here
http://wiki.fasterxml.com/JacksonFAQ#Serializing_Dates,
here
http://wiki.fasterxml.com/JacksonHowToCustomSerializers
here
http://jackson.codehaus.org/1.1.2/javadoc/org/codehaus/jackson/map/util/StdDateFormat.html
Using the #JsonSerialize(using= ... ) way:
public class JsonStdDateSerializer
extends JsonSerializer<Date> {
private static final DateFormat iso8601Format =
StdDateFormat.getBlueprintISO8601Format();
#Override
public void serialize(
Date date, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
// clone because DateFormat is not thread-safe
DateFormat myformat = (DateFormat) iso8601Format.clone();
String formattedDate = myformat.format(date);
jgen.writeString(formattedDate);
}
}
Declare the same Serializer used by Soap/XML:
#XmlElement(name = "prealert_date")
#XmlSchemaType(name = "dateTime")
#JsonSerialize(using = XMLGregorianCalendarSerializer.class)
protected XMLGregorianCalendar prealertDate;
In a previous similar question, I asked about, how to serialise two different sets of fields using JacksonJson and Spring.
My use case is the typical Controller mapping with #ResponseBody annotation returning directly a particular object or collections of objects, that are then rendered with JacksonJson whenever the client adds application/json in the Accept header.
I had two answers, the first one suggests to return different interfaces with a different getter list, the second suggests to use Json Views.
I don't have problems to understand the first way, however, for the second, after reading the documentation on JacksonJsonViews, I don't know how to implement it with Spring.
To stay with the example, I would declare three stub classes, inside the class Views:
// View definitions:
public class Views {
public static class Public { }
public static class ExtendedPublic extends PublicView { }
public static class Internal extends ExtendedPublicView { }
}
Then I've to declare the classes mentioned:
public class PublicView { }
public class ExtendedPublicView { }
Why on earth they declare empty static classes and external empty classes, I don't know. I understand that they need a "label", but then the static members of Views would be enough. And it's not that ExtendedPublic extends Public, as it would be logical, but they are in fact totally unrelated.
And finally the bean will specify with annotation the view or list of views:
//changed other classes to String for simplicity and fixed typo
//in classname, the values are hardcoded, just for testing
public class Bean {
// Name is public
#JsonView(Views.Public.class)
String name = "just testing";
// Address semi-public
#JsonView(Views.ExtendedPublic.class)
String address = "address";
// SSN only for internal usage
#JsonView(Views.Internal.class)
String ssn = "32342342";
}
Finally in the Spring Controller, I've to think how to change the original mapping of my test bean:
#RequestMapping(value = "/bean")
#ResponseBody
public final Bean getBean() {
return new Bean();
}
It says to call:
//or, starting with 1.5, more convenient (ObjectWriter is reusable too)
objectMapper.viewWriter(ViewsPublic.class).writeValue(out, beanInstance);
So I have an ObjectMapper instance coming out of nowhere and an out which is not the servlet typical PrintWriter out = response.getWriter();, but is an instance of JsonGenerator and that can't be obtained with the new operator. So I don't know how to modify the method, here is an incomplete try:
#RequestMapping(value = "/bean")
#ResponseBody
public final Bean getBean() throws JsonGenerationException, JsonMappingException, IOException {
ObjectMapper objectMapper = new ObjectMapper();
JsonGenerator out; //how to create?
objectMapper.viewWriter(Views.Public.class).writeValue(out, new Bean());
return ??; //what should I return?
}
So I would like to know if anybody had success using JsonView with Spring and how he/she did. The whole concept seems interesting, but the documentation seems lacking, also the example code is missing.
If it's not possible I will just use interfaces extending each others. Sorry for the long question.
Based on the answers by #igbopie and #chrislovecnm, I've put together an annotation driven solution:
#Controller
public class BookService
{
#RequestMapping("/books")
#ResponseView(SummaryView.class)
public #ResponseBody List<Book> getBookSummaries() {}
#RequestMapping("/books/{bookId}")
public #ResponseBody Book getBook(#PathVariable("bookId") Long BookId) {}
}
Where SummaryView is annotated on the Book model like so:
#Data
class Book extends BaseEntity
{
#JsonView(SummaryView.class)
private String title;
#JsonView(SummaryView.class)
private String author;
private String review;
public static interface SummaryView extends BaseView {}
}
#Data
public class BaseEntity
{
#JsonView(BaseView.class)
private Long id;
}
public interface BaseView {}
A custom HandlerMethodReturnValueHandler is then wired into Spring MVC's context to detect the #ResponseView annotation, and apply the Jackson view accordingly.
I've supplied full code over on my blog.
You need to manually wire in the MappingJacksonHttpMessageConverter. In spring 3.1 you are able to use the mvc xml tags like the following:
<mvc:annotation-driven >
<mvc:message-converter>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
</mvc:message-converters>
</mvc:annotation-driven>
It is pretty ugly to not use spring 3.1, it will save you about 20 lines of xml. The mvc:annotation tag does ALOT.
You will need to wire in the object mapper with the correct view writer. I have noticed recently the using a #Configuration class can make complicated wiring like this a lot easier. Use a #Configuration class and create a #Bean with your MappingJacksonHttpMessageConverter, and wire the reference to that bean instead of the MappingJacksonHttpMessageConverter above.
I've manage to solve the problem this way:
Create custom abstract class to contain the json response object:
public abstract AbstractJson<E>{
#JsonView(Views.Public.class)
private E responseObject;
public E getResponseObject() {
return responseObject;
}
public void setResponseObject(E responseObject) {
this.responseObject = responseObject;
}
}
Create a class for each visibility (just to mark the response):
public class PublicJson<E> extends AbstractJson<E> {}
public class ExtendedPublicJson<E> extends AbstractJson<E> {}
public class InternalJson<E> extends AbstractJson<E> {}
Change your method declaration:
#RequestMapping(value = "/bean")
#ResponseBody
public final PublicJson<Bean> getBean() throws JsonGenerationException, JsonMappingException, IOException {
return new PublicJson(new Bean());
}
Create customs MessageConverter:
public class PublicJsonMessageConverter extends MappingJacksonHttpMessageConverter{
public PublicApiResponseMessageConverter(){
super();
org.codehaus.jackson.map.ObjectMapper objMapper=new org.codehaus.jackson.map.ObjectMapper();
objMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
objMapper.setSerializationConfig(objMapper.getSerializationConfig().withView(Views.Public.class));
this.setObjectMapper(objMapper);
}
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
if(clazz.equals(PublicJson.class)){
return true;
}
return false;
}
}
public class ExtendedPublicJsonMessageConverter extends MappingJacksonHttpMessageConverter{
public ExtendedPublicJsonMessageConverter(){
super();
org.codehaus.jackson.map.ObjectMapper objMapper=new org.codehaus.jackson.map.ObjectMapper();
objMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
objMapper.setSerializationConfig(objMapper.getSerializationConfig().withView(Views.ExtendedPublic.class));
this.setObjectMapper(objMapper);
}
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
if(clazz.equals(ExtendedPublicJson.class)){
return true;
}
return false;
}
}
public class InternalJsonMessageConverter extends MappingJacksonHttpMessageConverter{
public InternalJsonMessageConverter(){
super();
org.codehaus.jackson.map.ObjectMapper objMapper=new org.codehaus.jackson.map.ObjectMapper();
objMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
objMapper.setSerializationConfig(objMapper.getSerializationConfig().withView(Views.Internal.class));
this.setObjectMapper(objMapper);
}
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
if(clazz.equals(Internal.class)){
return true;
}
return false;
}
}
Add the following to your xml:
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="PublicJsonMessageConverter"></bean>
<bean class="ExtendedPublicJsonMessageConverter"></bean>
<bean class="InternalJsonMessageConverter"></bean>
</mvc:message-converters>
</mvc:annotation-driven>
That's it! I had to update to spring 3.1 but that's all. I use the responseObject to send more info about the json call but you can override more methods of the MessageConverter to be completely transparent. I hope someday spring include an annotation for this.
Hope this helps!