Jackson - #JsonCreator return generics - java

I want to integrate vavr validation library in my command dto's in a way that when command dto is deserialized from request, return type of the static factory will be Try but jackson is throwing following error :
Type definition error: [simple type, class com.foo.command.FooCommand]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of com.foo.command.FooCommand (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
Here is FooCommand
#AllArgsConstructor(access = AccessLevel.PRIVATE)
public final class FooCommand {
private String foo;
private String bar;
#JsonCreator
public static Try<FooCommand> of(
#JsonProperty("foo") String foo,
#JsonProperty("bar") String bar
) {
return Try.of(() -> {
//Validate values
});
}
}
I am using spring 5 and it's annotated to deserialize request body automatically into controller parameter.
Is something like this possible ? Thanks in advance.

I had a similar problem that I fixed by using Converters: Using Jackson, how can I deserialize values using static factory methods that return wrappers with a generic type?
I haven't yet found how to apply the converters automatically, so you have to annotate every occurrence of the wrapped type in your requests.
public class Request {
#JsonDeserialize(converter = FooCommandConverter.class)
Try<FooCommand> command;
}
You can write a Converter like so:
public class FooCommandConverter
extends StdConverter<FooCommandConverter.DTO, Try<FooCommand>> {
#Override
public Try<FooCommand> convert(FooCommandConverter.DTO dto) {
return FooCommand.of(
dto.foo,
dto.bar
);
}
public static class DTO {
public String foo;
public String bar;
}
}

Related

Make Spring Boot JSON enum deserialization strict, so it does not silently convert invalid values into null

I am using Java Spring Boot #RestController with an object containing enum fields.
Spring automagically deserializes the JSON to the MyRequest object.
#RestController
public class MyController {
#PostMapping(path = "/operation")
public ResponseEntity<MyResponse> operation(#Valid #RequestBody MyRequest request) {
...
}
}
public class MyRequest {
private MyEnum1 field1;
private MyEnum2 field2;
private MyEnum3 field3;
private MyEnum4 field4;
private MyEnum5 field5;
private MyEnum6 field6;
... // really a lot of various enum fields!
}
public enum MyEnum1 {
VAL1, VAL2, VAL3;
}
The problem is that if the JSON contains completely invalid value of the enum field, the deserializer silently converts them to null, without any exception.
{
"field1": "BLAHBLAH",
...
}
This is user-unfriendly and treacherous.
I know that I may write custom JSON deserializers for each enum, but the solution is cumbersome and non-elegant.
Is there a way to globally set the JSON enum deserializer to a "strict mode", so if the value is invalid it throws an exception? If so, how and where?
That feature should be disabled by default.
But if you want to set it explicitly you can do it like this:
in your properties:
spring.jackson.deserialization.read-unknown-enum-values-as-null=false
or as an alternative in a configuration class (actually any bean would work, just make sure it happens early):
#Autowired
public void configureJackson(ObjectMapper objectMapper) {
objectMapper.disable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL);
}
Because it should actually be like this by default, I am wondering why it is not for you. Do you enable it somewhere? Which Spring Boot version are you using?

Override jackson #JsonValue from superclass

I have an interface (that already contains a Jackson annotation):
interface Interface {
#JsonValue
String fieldA();
String fieldB();
}
which I cannot modify, and a class that implements this interface:
class Impl implements Interface {
String fieldA;
String fieldB;
public Impl(String fieldA, String fieldB) {
this.fieldA = fieldA;
this.fieldB = fieldB;
}
#Override
#JsonSerialize
public String fieldA() {
return fieldA;
}
#Override
#JsonSerialize
public String fieldB() {
return fieldB;
}
}
Now, when I serialize the Impl class I would expect that the generated Json would have both fields (fieldA and fieldB) present.
This is not the case:
#Test
void should_serialize_both_fields() throws JsonProcessingException {
// Given
ObjectMapper mapper = new ObjectMapper();
Impl example = new Impl("test", "test");
String expected = "{\"fieldA\": \"test\", \"fieldB\": \"test\"}";
// When
String json = mapper.writeValueAsString(example);
// Then
org.assertj.core.api.Assertions.assertThat(json).isEqualTo(expected);
}
In this test the resulting json is "test" instead of {"fieldA": "test", "fieldB": "test"}:
org.opentest4j.AssertionFailedError:
Expecting:
<""test"">
to be equal to:
<"{"fieldA": "test", "fieldB": "test"}">
but was not.
The problem comes from the already present #JsonValue annotation on the interface, which I cannot modify. Also, if I try to annotate another method in Impl then I get this exception from jackson:
com.fasterxml.jackson.databind.JsonMappingException: Problem with definition of [AnnotedClass com.actility.m2m.commons.service.error.InternalErrorCodeImplTest$Impl]: Multiple 'as-value' properties defined ([method com.actility.m2m.commons.service.error.InternalErrorCodeImplTest$Impl#fieldB(0 params)] vs [method com.actility.m2m.commons.service.error.InternalErrorCodeImplTest$Impl#fieldA(0 params)])
Is there any way to achieve this?
Going by the docs, you should be able to set a "false" JSON value in the subclass:
Boolean argument is only used so that sub-classes can "disable" annotation if necessary.
I guess that already tells you all you need to know, but here's what it would look like:
class Impl implements Interface {
//...
#Override
#JsonSerialize
#JsonValue(false) //...disables inherited annotation
public String fieldA() {
return fieldA;
}
// ...
}

Jackson create object by class name

Is there opportunity to read from json class name and create and object?
Here is what I mean:
I have an interface
public interface Converter {
void process();
}
Next I also have some data class
public class Source {
private String service;
private String path;
private Converter converter;
}
And a class that implements Converter interface
public class DataConverter implements Converter {
public void process() {
//some code here
}
}
Last but not least. This is part of my json:
"source": {
"service": "VIS",
"path": "/",
"converter": "DataConverter"
}
So the idea is while reading Json via Jackson's mapper.readValue create a DataConverter so it will be available from the Data class via getter.
Thanks!
You can achieve this by writing custom serialisers and deserialisers, and then annotating the field in your Source class. To do this you need to implement the Converter interface. The documentation suggests:
NOTE: implementors are strongly encouraged to extend StdConverter instead of directly implementing Converter, since that can help with default implementation of typically boiler-plate code.
So what you want to do is something like this for the custom Serialiser:
public class ConverterSerializer extends StdConverter<Converter, String> {
#Override
public String convert(Converter value) {
if(value instanceof DataConverter) {
return "DataConverter";
} ...
return "";
}
}
And then annotate the value with #JsonSerialize:
#JsonSerialize(using = ConverterSerializer.class)
private Converter converter;
The same applies for deserialising but you would implement an StdConverter<String,Converter> for which the convert method will take a String and return a Converter. You would then annotate the converter field with #JsonDeserialize and reference the converter.

Custom deserialization of path variable in spring web starter

What is the correct way to register a custom deserializer for a path variable in Spring web reactive?
Example:
#GetMapping("test/{customType}")
public String test(#PathVariable CustomType customType) { ...
I tried ObjectMapperBuilder, ObjectMapper and directly via #JsonDeserialize(using = CustomTypeMapper.class) but it wont register:
Response status 500 with reason "Conversion not supported."; nested exception is org.springframework.beans.ConversionNotSupportedException: Failed to convert value of type 'java.lang.String' to required type '...CustomType'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type '...CustomType': no matching editors or conversion strategy found
For path variables deserialization you don't need to involve jackson, you can use org.springframework.core.convert.converter.Converter
For example:
#Component
public class StringToLocalDateTimeConverter
implements Converter<String, LocalDateTime> {
#Override
public LocalDateTime convert(String source) {
return LocalDateTime.parse(
source, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}
}
#GetMapping("/findbydate/{date}")
public GenericEntity findByDate(#PathVariable("date") LocalDateTime date) {
return ...;
}
Here's an article with examples
I ended up using #ModelValue because it semantically is not a deserialization, just parsing a key.
#RestController
public class FooController {
#GetMapping("test/{customType}")
public String test(#ModelAttribute CustomType customType) { ... }
}
#ControllerAdvice
public class GlobalControllerAdvice {
#ModelAttribute("customType")
public CustomType getCustomType(#PathVariable String customType) {
CustomeType result = // map value to object
return result;
}
}

How to (De)serialize field from object based on annotation using Jackson?

I need to configure Jackson in a specific way which I'll describe below.
Requirements
Annotated fields are serialized with only their id:
If the field is a normal object, serialize its id
If the field is a collection of objects, serialize an array of id
Annotated fields get their property names serialized differently:
If the field is a normal object, add "_id" suffix to property name
If the field is a collection of objects, add "_ids" suffix to property name
For the annotation I was thinking something like a custom #JsonId, ideally with an optional value to override the name just like #JsonProperty does
The id property should be defined by the user, either using:
The already existing Jackson's #JsonIdentityInfo
Or by creating another class or field annotation
Or by deciding which annotation to inspect for id property discoverability (useful for JPA scenarios, for example)
Objects should be serialized with a wrapped root value
Camel case naming should be converted to lower case with underscores
All of this should be deserializable (by constructing an instance with just the id setted)
An example
Considering these POJO's:
//Inform Jackson which property is the id
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id"
)
public abstract class BaseResource{
protected Long id;
//getters and setters
}
public class Resource extends BaseResource{
private String name;
#JsonId
private SubResource subResource;
#JsonId
private List<SubResource> subResources;
//getters and setters
}
public class SubResource extends BaseResource{
private String value;
//getters and setters
}
A possible serialization of a Resource instance could be:
{
"resource":{
"id": 1,
"name": "bla",
"sub_resource_id": 2,
"sub_resource_ids": [
1,
2,
3
]
}
}
So far...
Requirement #5 can be accomplished by configuring ObjectMapper in the following way:
objectMapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
And then using #JsonRootName("example_root_name_here") in my POJO's.
Requirement #6 can be accomplished by configuring ObjectMapper in the following way:
objectMapper.setPropertyNamingStrategy(
PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
As you can see there are still lots of requirements to fulfill. For those wondering why I need such a configuration, it's because I'm developing a REST webservice for ember.js (more specifically Ember Data).
You would appreciate very much if you could help with any of the requirements.
Thanks!
Most (all?) of your requirements can be accomplished through the use of a contextual serializer. Taking one answer from ContextualDeserializer for mapping JSON to different types of maps with Jackson and Jackson's wiki (http://wiki.fasterxml.com/JacksonFeatureContextualHandlers) I was able to come up with the following.
You need to start with the #JsonId annotation, which is the key indicating a property needs to only use the Id property.
import com.fasterxml.jackson.annotation.*;
import java.lang.annotation.*;
#Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#JacksonAnnotation // important so that it will get included!
public #interface JsonId {
}
Next is the actual ContextualSerializer, which does the heavy lifting.
import com.fasterxml.jackson.databind.ser.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.core.*;
import java.io.*;
public class ContextualJsonIdSerializer
extends JsonSerializer<BaseResource>
implements ContextualSerializer/*<BaseResource>*/
{
private ObjectMapper mapper;
private boolean useJsonId;
public ContextualJsonIdSerializer(ObjectMapper mapper) { this(mapper, false); }
public ContextualJsonIdSerializer(ObjectMapper mapper, boolean useJsonId) {
this.mapper = mapper;
this.useJsonId = useJsonId;
}
#Override
public void serialize(BaseResource br, JsonGenerator jgen, SerializerProvider provider) throws IOException
{
if ( useJsonId ) {
jgen.writeString(br.getId().toString());
} else {
mapper.writeValue(jgen, br);
}
}
#Override
public JsonSerializer<BaseResource> createContextual(SerializerProvider config, BeanProperty property)
throws JsonMappingException
{
// First find annotation used for getter or field:
System.out.println("Finding annotations for "+property);
if ( null == property ) {
return new ContextualJsonIdSerializer(mapper, false);
}
JsonId ann = property.getAnnotation(JsonId.class);
if (ann == null) { // but if missing, default one from class
ann = property.getContextAnnotation(JsonId.class);
}
if (ann == null ) {//|| ann.length() == 0) {
return this;//new ContextualJsonIdSerializer(false);
}
return new ContextualJsonIdSerializer(mapper, true);
}
}
This class looks at BaseResource properties and inspects them to see if the #JsonId annotation is present. If it is then only the Id property is used, otherwise a passed in ObjectMapper is used to serialize the value. This is important because if you try to use the mapper that is (basically) in the context of the ContextualSerializer then you will get a stack overflow since it will eventually call these methods over and over.
You're resource should look something like the following. I used the #JsonProperty annotation instead of wrapping the functionality in the ContextualSerializer because it seemed silly to reinvent the wheel.
import java.util.*;
import com.fasterxml.jackson.annotation.*;
public class Resource extends BaseResource{
private String name;
#JsonProperty("sub_resource_id")
#JsonId
private SubResource subResource;
#JsonProperty("sub_resource_ids")
#JsonId
private List<SubResource> subResources;
//getters and setters
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public SubResource getSubResource() {return subResource;}
public void setSubResource(SubResource subResource) {this.subResource = subResource;}
public List<SubResource> getSubResources() {return subResources;}
public void setSubResources(List<SubResource> subResources) {this.subResources = subResources;}
}
Finally the method that performs the serialization just creates an additional ObjectMapper and registers a module in the original ObjectMapper.
// Create the original ObjectMapper
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
// Create a clone of the original ObjectMapper
ObjectMapper objectMapper2 = new ObjectMapper();
objectMapper2.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
objectMapper2.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
objectMapper2.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
// Create a module that references the Contextual Serializer
SimpleModule module = new SimpleModule("JsonId", new Version(1, 0, 0, null));
// All references to SubResource should be run through this serializer
module.addSerializer(SubResource.class, new ContextualJsonIdSerializer(objectMapper2));
objectMapper.registerModule(module);
// Now just use the original objectMapper to serialize

Categories