My jhipster v2.23.1 app uses custom serializers and deserializers for JSON parsing which I register as a module in JacksonConfiguration. The REST API works as expected using my custom mapping.
However, the JSON displayed in the auto-generated swagger documentation doesn't reflect the custom mapping. I was hoping swagger would detect custom serializers/deserializers automatically, but since it doesn't, how can I get swagger to show my custom JSON format instead of the one it detects on its own?
Based on the springfox documentation at http://springfox.github.io/springfox/docs/current/#configuring-springfox i've implemented the interface:
ApplicationListener<ObjectMapperConfigured>
in my SwaggerConfiguration bean. I can see that the onApplicationEvent(ObjectMapperConfigured event) method is called twice. The first time the mapper will serialize my object as expected, the second time it will not. It also doesn't seem to make a difference if I register my module with the mapper or not. The object I'm working with here is a Contact.
#Override
public void onApplicationEvent(ObjectMapperConfigured event) {
ObjectMapper mapper = event.getObjectMapper();
// Custom serialization for Contact objects
SimpleModule contactModule = new SimpleModule("Contact Module");
contactModule.addSerializer(new ContactSerializer(Contact.class));
contactModule.addDeserializer(Contact.class, new ContactDeserializer(Contact.class));
mapper.registerModule(contactModule);
// My custom object
Contact c = new Contact();
c.setCity("Springfield");
c.setEmail("someone#gmail.com");
String contactJsonStr = null;
try {
contactJsonStr = mapper.writeValueAsString(c);
} catch(JsonProcessingException e) {
e.printStackTrace();
}
System.out.println("Serialized Contact: " + contactJsonStr);
}
How can I get springfox to use my custom serializer in order to build my swagger documentation? Or should I be using a different approach entirely?
Hey I know this is an old question but i stumbled uppon the same problem and done a little research.
The solution is quite simple. Write a class wich represents your custom serialized object. Then just use the directModelSubstitute method in your Docket method to substitute your original model class with the serialized model.
If your serializer does something like this to serialise the DateTime into UNIX Time (Long)
public void serialize(final DateTime value, final JsonGenerator gen, final SerializerProvider serializers) throws IOException, JsonProcessingException {
long millis = value.getMillis();
gen.writeNumber(millis);
}
Just add .directModelSubstitute(DateTime.class, Long.class) this line to your Docket definition.
Related
I have found how to customize ObjectMapper date format in order to let Spring to help to auto serialize/deserialize (serialize when I want to return object to client, deserialize when the request body is json object), but I have lot of DTO with different date format, some might need yyyy-mm-dd, some is dd-mm-yyyy, one ObjectMapper will not work for different required date format, what is the best practice solution for this issue?
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
super.configureMessageConverters(converters);
converters.add(mappingJacksonHttpMessageConverter());
}
MappingJacksonHttpMessageConverter mappingJacksonHttpMessageConverter() {
MappingJacksonHttpMessageConverter mappingJacksonHttpMessageConverter = new MappingJacksonHttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setDateFormat(new SimpleDateFormat("dd-MM-yyyy"));
objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
mappingJacksonHttpMessageConverter.setObjectMapper(objectMapper);
mappingJacksonHttpMessageConverter.setPrettyPrint(true);
return mappingJacksonHttpMessageConverter;
}
You could use custom Serializers and handle the different formats within a single Serializer. Here are a few pages that have some info on how to create custom Serializer/Deserializers:
Create Custom Serializer
Create Custom Deserializer
-- Edit --
From the documentation for MappingJacksonHttpMessageConverter (some emphasis added):
setObjectMapper
public void setObjectMapper(org.codehaus.jackson.map.ObjectMapper objectMapper)
Set the ObjectMapper for this view. If not set, a default ObjectMapper is used.
Setting a custom-configured ObjectMapper is one way to take further control
of the JSON serialization process. For example, an extended SerializerFactory
can be configured that provides custom serializers for specific types.
The other option for refining the serialization process is to use Jackson's
provided annotations on the types to be serialized, in which case a
custom-configured ObjectMapper is unnecessary.
This means that you do not even need to call setObjectMapper if you have Serializers/Deserializers defined by annotations (as described in the links I posted above). For your benefit, here is an example:
For Serializing:
Create a StdSerializer object to handle the type you are interested in
public class ItemSerializer extends StdSerializer<Item> {
// ...
#Override
public void serialize(Item value, JsonGenerator jgen, SerializerProvider provider) {
// Write the Item data into the JsonGenerator
}
}
Define the Serializer for the object via annotations
#JsonSerialize(using = ItemSerializer.class)
public class Item {
// ...
}
For Deserialization
Create a StdDeserializer object to handle the type you are interested in
public class ItemDeserializer extends StdDeserializer<Item> {
// ...
#Override
public Item deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
// Handle the different date formats here!
return new Item(/*parsed date object*/);
}
}
Define the Deserializer for the object via annotations
#JsonDeserialize(using = ItemDeserializer.class)
public class Item {
// ...
}
I am getting data from an external JSON API and parsing the result with Jackson. Unfortunately, that API returns all fields as String and those are filled with "N/A" when data is unavailable.
I would like to replace those fields with null, especially as those are frustrating when I try to convert JSON String fields to more informative Java fields.
A custom DeserializationProblemHandler worked for Integer fields (see below), but it was useless for Java 8's LocalDate fields. Furthermore, it reacts to a problem rather than anticipating it.
I was unable to find a pre-processor to configure into my ObjectMapper and am uneasy with the idea of overriding the BeanDeserializer.
Do you know of a better/cleaner solution to handle this kind of situations? Thanks!
DeserializationProblemHandler
new DeserializationProblemHandler() {
#Override
public Object handleWeirdStringValue(DeserializationContext ctxt, Class<?> targetType, String valueToConvert, String failureMsg) throws IOException {
return "N/A".equals(valueToConvert) ? null : super.handleWeirdStringValue(ctxt, targetType, valueToConvert, failureMsg);
}
}
Error message when processing "N/A" in LocalDate field
Can not deserialize value of type java.time.LocalDate from String "N/A": Text 'N/A' could not be parsed at index 0
(works fine when there is date in the data)
I feel like there ought to be a better way of doing this, but the following is the only solution I was able to come up with.
Create a new JsonDeserializer that handles "N/A" input. The following example handles strings:
public class EmptyStringDeserializer extends StdScalarDeserializer<String> {
public EmptyStringDeserializer() {
super(String.class);
}
#Override
public String deserialize(JsonParser parser, DeserializationContext ctx) throws IOException {
final String val = parser.getValueAsString();
if ("N/A".equalsIgnoreCase(val))
return null;
return val;
}
}
The class is registered with an ObjectMapper like this:
SimpleModule simpleModule = new SimpleModule().addDeserializer(String.class, new EmptyStringDeserializer());
ObjectMapper om = new ObjectMapper().registerModule(simpleModule);
You'll probably want to collect all your converters in a module named for the API that is making you handle things this way.
So, in our project, we are using a #JsonView to exclude the attributes that we don't want at each request from being serialiazed/called. The problem is that this is very crude, and we need to create a new View or annotate on the Model every time we need something different, following this example: very nice guy with a blog.
We believe that this is not really optimal, and a pretty boilerplate-y way to do it, so we're looking to change that logic: instead of the server responding only what it wants, we want to request the server to respond only what the client wants, to move that responsability from the server. Instead of doing a simple $.get, i'd pass on the parameters of that $.get the attributes that i want at the moment (i.e. only name and picture, sometimes full-information for editing and etc). I know that this is possible, but i haven't been able to find any ways for doing this using Spring.
We're using Spring 4.2 and Jackson 2.6.1 and consuming the REST with AngularJS, so this would really help. Any thoughts on how to do this, or any guidance? Really thanks for the help!
GET request:
{
username,
picture
}
And receive a json response:
{
"id":1,
"username":"john",
"picture":"/john.png"
}
Something along these lines.
Your question looks very similar to:
Spring Rest Controller Return Specific Fields
Just pass fields you are interested in, and return composed map according to that fields.
If the serialization/deserialization strategy doesn`t work, then you should consider using a builder. It will be pretty straight forward to develop what you want, but no magic. Some alternatives: https://github.com/ralfstx/minimal-json , http://www.javabeat.net/java-json-api-jsr-353/ , https://github.com/Homyk/JsonBuilder/wiki/JsonBuilder-WIKI .
I also needed similar implementation and did this using Jackson. I used custom serializer and java reflection. No change in model classes is needed.
// my sample object
public class A {
private int _1;
private int _2;
private int _3;
public A(int _1, int _2, int _3) {
this._1 = _1;
this._2 = _2;
this._3 = _3;
}
.. getters .. setters
}
Custom serializer for my class.
public class ASerializer extends JsonSerializer<A> {
private final List<String> properties;
public ASerializer(String... properties) {
this.properties = Arrays.asList(properties);
}
#Override
public Class<A> handledType() {
return A.class;
}
#Override
public void serialize(A a, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeStartObject();
/* wanted property list is provided through constructor of serializer */
for (String property : this.properties) {
try {
/* field values are accessed by reflection */
Field field = a.getClass().getDeclaredField(property);
field.setAccessible(true);
Object value = field.get(a);
jgen.writeObjectField(property, value);
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) {
Logger.getLogger(ASerializer.class.getName()).log(Level.SEVERE, null, ex);
}
}
jgen.writeEndObject();
}
}
Usage:
public class App {
public static void main(String... args) throws JsonProcessingException, IOException {
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
/* initialize object */
A a = new A(1, 2, 3);
/* initialize a new serializer object with wanted properties and add to module */
module.addSerializer(new ASerializer(new String[]{"_1", "_2"}));
/* if you want to flush cached serializers */
DefaultSerializerProvider dsp = (DefaultSerializerProvider) objectMapper.getSerializerProvider();
dsp.flushCachedSerializers();
objectMapper.registerModule(module);
System.out.println(objectMapper.writeValueAsString(a));
}
}
Outputs:
{"_1":1,"_2":2}
I am storing test data for a RESTful endpoint in json. I will be deserializing the test data into POJOs to pass into REST Assured for testing. Underneath the hood, REST Assured serializes the POJOs using Jackson when constructing request objects.
I am using the Guava datatype module to distinguish between fields which are present in the json but set to null and those which are not present at all. When json is deserialized to a POJO using jackson with the guava datatype module for Optional fields, fields which are missing from the json are set to null in the POJO, whereas those fields which are present in the json with value set explicitly to null are set to Optional.absent().
When the POJO is serialized by Rest Assured (with the help of Jackson under the hood) to construct the HTTP Reguest object, I want the 'missing' fields (null in the POJO) to be ignored, but those fields which are present, but set explicitly to null (Optional.absent() in the POJO) to be serialized. I cannot use the #JsonInclude(Include.NON_NULL) annotation because it excludes both cases, due to the way that the Serializer for Guava datatypes in the Guava datatype module handles them. I want a way to override this behavior while preserving my ability to use the module. I'm not sure how to do it.
Here is example of what I am talking about:
public class Item {
private Optional<Integer> id;
private Optional<String> name;
private Optional<String> description;
}
{ "name":null, "description":"Something" }
The above POJO would be instantiated like this once the json String above is deserialized:
Item:
id = null
name = Optional.<String>absent()
description = "Something"
When the POJO is serialized back to json, I want the output to be what it is in test data:
{ "name":null, "description":"Something" }
However, what I get is this:
{ "id":null, "name":null, "description":"Something" }
If I use the #JsonInclude(Include.NON_NULL), I get this:
{ "description":"Something" }
UPDATE:
So, I basically hacked the module to do what I wanted. Here are the alterations I made:
In GuavaOptionalSerializer, I altered isEmpty(Optional<?> value) to return true only if the value was null and not if is either null or absent.
#Override
public boolean isEmpty(Optional<?> value) {
return (value == null);
}
And in GuavaOptionalBeanPropertyWriter I altered the serializeAsField method to first handle suppressing empty values and then to handle null values:
#Override
public void serializeAsField(Object bean, JsonGenerator jgen, SerializerProvider prov) throws Exception
{
// and then see if we must suppress certain values (default, empty)
if (_suppressableValue != null) {
super.serializeAsField(bean, jgen, prov);
return;
}
if (_nullSerializer == null) {
Object value = get(bean);
if (Optional.absent().equals(value)) {
return;
}
}
super.serializeAsField(bean, jgen, prov);
}
Now, if I want to include the absent fields (explicitly set to null) and exclude the fields which are simple 'missing', I use the #JsonInclude(Include.NON_EMPTY) annotation.
Unfortunately, looking at the source for the serializer, I don't see there being an out-of-the-box way to do what you're looking for. Optional.absent() just delegates to the default null-handling behavior, which will respect the #JsonInclude annotation.
This is reasonable, because the whole motivation of Optional<> is that you shouldn't assign null to an Optional<> reference.
However, you can always write your own serializer to do this for you. The implementation would be much like the linked GuavaOptionalSerializer, but you would need to alter this function:
#Override
public void serialize(Optional<?> value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonGenerationException {
if(value.isPresent()){
provider.defaultSerializeValue(value.get(), jgen);
} else{
// provider.defaultSerializeNull(jgen);
// force writing null here instead of using default
}
}
I have a POJO
class Product {
String name;
Size size;
}
So, I want to map a deserialize a JSON to my POJO. If I have both the attributes in my JSON, it is not a problem.
But in my case, sometimes size will not be a part of the JSON. There might be a third attribute 'type' based on which I will set my size. I do not want to include 'type' in my POJO. Are there any Jackson annotations which can do this?
write your custom Deserializers:
SimpleModule module =
new SimpleModule("ProductDeserializerModule",
new Version(1, 0, 0, null));
module.addDeserializer(Product.class, new ProductJsonDeserializer());
mapper = new ObjectMapper();
mapper.registerModule(module);
//...
class ProductJsonDeserializer extends JsonDeserializer<Product>
{
#Override
public Product deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException
{
// handle here if exist a third attribute 'type' and create the product
}
}
More info here: http://wiki.fasterxml.com/JacksonHowToCustomDeserializers
Found a pretty simple solution for this!
When a JSON attribute is attempted to be mapped to my POJO's attribute, it just checks whether a setter exists for it.
For example, if there is an attribute type in JSON, it will try to hit a method named setType(obj) in my POJO, regardless of whether there exists an attribute named type.
This worked for me! I simply set my other attributes inside this setter.