Spring #RestController custom JSON deserializer - java

I want to use custom JSON deserializer for some classes(Role here) but I can't get it working. The custom deserializer just isn't called.
I use Spring Boot 1.2.
Deserializer:
public class ModelDeserializer extends JsonDeserializer<Role> {
#Override
public Role deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
return null; // this is what should be called but it isn't
}
}
Controller:
#RestController
public class RoleController {
#RequestMapping(value = "/role", method = RequestMethod.POST)
public Object createRole(Role role) {
// ... this is called
}
}
#JsonDeserialize on Role
#JsonDeserialize(using = ModelDeserializer.class)
public class Role extends Model {
}
Jackson2ObjectMapperBuilder bean in Java Config
#Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.deserializerByType(Role.class, new ModelDeserializer());
return builder;
}
What am I doing wrong?
EDIT It is probably caused by #RestController because it works with #Controller...

First of all you don't need to override Jackson2ObjectMapperBuilder to add custom deserializer. This approach should be used when you can't add #JsonDeserialize annotation. You should use #JsonDeserialize or override Jackson2ObjectMapperBuilder.
What is missed is the #RequestBody annotation:
#RestController
public class JacksonCustomDesRestEndpoint {
#RequestMapping(value = "/role", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public Object createRole(#RequestBody Role role) {
return role;
}
}
#JsonDeserialize(using = RoleDeserializer.class)
public class Role {
// ......
}
public class RoleDeserializer extends JsonDeserializer<Role> {
#Override
public Role deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
// .................
return something;
}
}

There is also another pretty interesting solution which can be helpful in case when you want to modify your JSON body before calling default deserializer. And let's imagine that you need to use some additional bean for that (use #Autowire mechanism)
Let's image situation, that you have the following controller:
#RequestMapping(value = "/order/product", method = POST)
public <T extends OrderProductInterface> RestGenericResponse orderProduct(#RequestBody #Valid T data) {
orderService.orderProduct(data);
return generateResponse();
}
Where OrderProductInterface is:
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonSerialize(include = NON_EMPTY)
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, visible = true, property = "providerType")
#JsonSubTypes({
#JsonSubTypes.Type(value = OrderProductForARequestData.class, name = "A")
})
public interface OrderProductInterface{}
The code above will provide dynamic deserialization base on filed providerType and validation according to concrete implementation. For better grasp, consider that OrderProductForARequestData can be something like that:
public class OrderProductForARequestData implements OrderProductInterface {
#NotBlank(message = "is mandatory field.")
#Getter #Setter
private String providerId;
#NotBlank(message = "is mandatory field.")
#Getter #Setter
private String providerType;
#NotBlank(message = "is mandatory field.")
#Getter #Setter
private String productToOrder;
}
And let's image now that we want to init somehow providerType (enrich input) before default deserialization will be executed. so the object will be deserialized properly according to the rule in OrderProductInterface.
To do that you can just modify your #Configuration class in the following way:
//here can be any annotation which will enable MVC/Boot
#Configuration
public class YourConfiguration{
#Autowired
private ObjectMapper mapper;
#Autowired
private ProviderService providerService;
#Override
public void setup() {
super.setup();
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
#Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
if (beanDesc.getBeanClass() == OrderProductInterface.class) {
return new OrderProductInterfaceDeserializer(providerService, beanDesc);
}
return deserializer;
}
});
mapper.registerModule(module);
}
public static class OrderProductInterfaceDeserializer extends AbstractDeserializer {
private static final long serialVersionUID = 7923585097068641765L;
private final ProviderService providerService;
OrderProductInterfaceDeserializer(roviderService providerService, BeanDescription beanDescription) {
super(beanDescription);
this.providerService = providerService;
}
#Override
public Object deserializeWithType(JsonParser p, DeserializationContext context, TypeDeserializer typeDeserializer) throws IOException {
ObjectCodec oc = p.getCodec();
JsonNode node = oc.readTree(p);
//Let's image that we have some identifier for provider type and we want to detect it
JsonNode tmp = node.get("providerId");
Assert.notNull(tmp, "'providerId' is mandatory field");
String providerId = tmp.textValue();
Assert.hasText(providerId, "'providerId' can't be empty");
// Modify node
((ObjectNode) node).put("providerType",providerService.getProvider(providerId));
JsonFactory jsonFactory = new JsonFactory();
JsonParser newParser = jsonFactory.createParser(node.toString());
newParser.nextToken();
return super.deserializeWithType(newParser, context, typeDeserializer);
}
}
}

Related

Spring boot validation for interfaces and extended ArrayList

I am using spring-boot with hibernate validator
I have a controller annotated with #Validated and I want to validate all the params in my APIs.
One of the annotation is not working(I debugged and it just never being checked as if it is not annotated at all).
public MyResponse getPolicyCounters(#RequestBody #Valid MyRequest request) throws Exception {
The MyRequest class looks like this:
#Valid
public class MyRequest{
boolean shouldSumSubRules;
private #Valid RulesSelector rules;
The RuleSelector class is an interface and looks like this:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
#JsonSubTypes({
#JsonSubTypes.Type(value = RulesStaticList.class, name = "list"),
#JsonSubTypes.Type(value = RulesDynamicList.class, name = "criteria")
})
#Valid
public interface RulesSelector {
}
The RuleStaticList class looks like this:
#Valid
public class RulesStaticList extends ArrayList<#Valid RuleReference> implements RulesSelector {
}
The RuleRefrence class looks like this:
#JsonDeserialize(using = RuleReferenceDeserializer.class)
#Valid
public interface RuleReference {
RuleReferenceKind getKind();
}
final class RuleReferenceDeserializer extends StdDeserializer<RuleReference> {
public RuleReferenceDeserializer() {
super(RuleReference.class);
}
#Override
public RuleReference deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String fieldName = p.nextFieldName();
switch (fieldName) {
case "tag":
return p.readValueAs(RuleReferenceByTag.class);
case "special":
return p.readValueAs(RuleReferenceBySpecial.class);
default:
throw ctxt.wrongTokenException(p, RuleReference.class, JsonToken.FIELD_NAME, "");
}
}
}
I have 2 implementation for RuleReference one of them is RuleReferenceByTag and it looks like this:
#Valid
public class RuleReferenceByTag implements RuleReference {
#Size(max = 128)
private String tag;
public RuleReferenceKind getKind() { return RuleReferenceKind.Tag; }
}
I added #Valid annotation everywhere I could but it still does not work.
What am I missing?
Other annotation in with different classes it does work but I could not solve this problem :/

Jackson Module Deserializer not called

I am trying to write Jackson deserializer module in Spring Boot app.
The main reason is to encrypt pin number from incoming request by using custom Jackson deserializer.
Encryption properties are provided by spring component CipherInterface
I was trying solution from this but my custom deserializer still was not called.
Instead of this based StringDeserializer is always called and no encryption is performed
Thanks in advance
Annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
#JacksonAnnotation
public #interface Encrypted {
}
Request body with field to be encrypted
#Value
public class CardCounterDecreaseRequest {
#Encrypted
private final String pinValue;
}
Jackson configuration
#Bean
ObjectMapper unrestrictObjectMapper(final CipherInterface cipherInterface) {
return JsonMapper.builder()
.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS)
.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
.enable(SerializationFeature.INDENT_OUTPUT)
.enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)
.enable(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)
.disable(DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.disable(DeserializationFeature.ACCEPT_FLOAT_AS_INT)
.visibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE)
.visibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
.addModules(new EncryptionModule(cipherInterface), new JavaTimeModule(), new VavrModule(), new ParanamerModule())
.build();
}
Module:
public class EncryptionModule extends SimpleModule {
private final CipherInterface cipher;
public EncryptionModule(final CipherInterface cipher) {
super();
this.cipher = cipher;
}
#Override
public void setupModule(final SetupContext context) {
context.addBeanDeserializerModifier(new EncryptedDeserializerModifier(cipher));
}
}
public class EncryptedDeserializerModifier extends BeanDeserializerModifier {
private final CipherInterface cipher;
public EncryptedDeserializerModifier(final CipherInterface cipher) {
super();
this.cipher = cipher;
}
#Override
public BeanDeserializerBuilder updateBuilder(final DeserializationConfig config,
final BeanDescription beanDesc,
final BeanDeserializerBuilder builder) {
final Iterator<SettableBeanProperty> it = builder.getProperties();
while (it.hasNext()) {
final SettableBeanProperty prop = it.next();
if (null != prop.getAnnotation(Encrypted.class)) {
final JsonDeserializer<Object> current = prop.getValueDeserializer(); // always returns null
final EncryptedJsonDeserializer encryptedDeserializer = new EncryptedJsonDeserializer(cipher, current);
final SettableBeanProperty propWithEncryption = prop.withValueDeserializer(encryptedDeserializer);
builder.addOrReplaceProperty(propWithEncryption, true);
}
}
return builder;
}
}
And finally deserializer:
public class EncryptedJsonDeserializer extends JsonDeserializer<Object> implements ContextualDeserializer {
private final CipherInterface service;
private final JsonDeserializer<Object> baseDeserializer;
private final BeanProperty property;
public EncryptedJsonDeserializer(final CipherInterface service, final JsonDeserializer<Object> baseDeserializer) {
this.service = service;
this.baseDeserializer = baseDeserializer;
this.property = null;
}
public EncryptedJsonDeserializer(final CipherInterface service, final JsonDeserializer<Object> wrapped, final BeanProperty property) {
this.service = service;
this.baseDeserializer = wrapped;
this.property = property;
}
#Override
public Object deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonDeserializer<?> deserializer = baseDeserializer;
if (deserializer instanceof ContextualDeserializer) {
deserializer = ((ContextualDeserializer) deserializer).createContextual(ctxt, property);
}
return // encryption logic here
}
#Override
public JsonDeserializer<?> createContextual(final DeserializationContext ctxt, final BeanProperty property) throws JsonMappingException {
JsonDeserializer<Object> wrapped = ctxt.findRootValueDeserializer(property.getType());
return new EncryptedJsonDeserializer(service, wrapped, property);
}
Just try below code, as you had created deserializer correctly but you are not informing spring that while deserialize this entity use below Custom desierializer class.
Add this extra line #JsonDeserialize(using = EncryptedJsonDeserializer.class) and try once.
#Value
#JsonDeserialize(using = EncryptedJsonDeserializer.class)
public class CardCounterDecreaseRequest {
#Encrypted
private final String pinValue;
}
It will help you.

How to deserialize JSON to interface?

I have trouble with deserialization JSON to some of classes ChildA, ChildB and etc. that implements Basic interface in following example.
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = InstagramUser.class, name = "ChildA")
})
public interface Basic {
getName();
getCount();
}
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeName("ChildA")
public class ChildA implements Basic { ... }
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeName("ChildB")
public class ChildB implements Basic { ... }
...
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Response<E extends Basic> {
#JsonProperty("data")
private List<E> data;
public List<E> getData() {
return data;
}
public void setData(List<E> data) {
this.data = data;
}
}
// deserialization
HTTPClient.objectMapper.readValue(
response,
(Class<Response<ChildA>>)(Class<?>) Response.class
)
Exception is: com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (END_OBJECT), expected FIELD_NAME: missing property 'type' that is to contain type id (for class Basic)
Expected JSON is like this:
{
"data": [{ ... }, ...]
}
There is no property that is presented in all type objects so they are completely different. But as you can see on readValue line I know what is expected type. How to structure JsonTypeInfo and JsonSubTypes annotaions to deserialize JSON as expected class?
I kinda had the same problem as you, based in the reading here: Jackson Deserialize Abstract Classes I created my own solution, it basically consists of creating my own deserializer, the trick is to use/identify a specific property within JSON to know which instance type should be returned from deserialization, example is:
public interface Basic {
}
First Child:
public class ChildA implements Basic {
private String propertyUniqueForThisClass;
//constructor, getters and setters ommited
}
SecondChild:
public class ChildB implements Basic {
private String childBUniqueProperty;
//constructor, getters and setters ommited
}
The deserializer (BasicDeserializer.java) would be like:
public class BasicDeserializer extends StdDeserializer<Basic> {
public BasicDeserializer() {
this(null);
}
public BasicDeserializer(final Class<?> vc) {
super(vc);
}
#Override
public Basic deserialize(final JsonParser jsonParser,
final DeserializationContext deserializationContext)
throws IOException {
final JsonNode node = jsonParser.getCodec().readTree(jsonParser);
final ObjectMapper mapper = (ObjectMapper) jsonParser.getCodec();
// look for propertyUniqueForThisClass property to ensure the message is of type ChildA
if (node.has("propertyUniqueForThisClass")) {
return mapper.treeToValue(node, ChildA.class);
// look for childBUniqueProperty property to ensure the message is of type ChildB
} else if (node.has("childBUniqueProperty")) {
return mapper.treeToValue(node, ChildB.class);
} else {
throw new UnsupportedOperationException(
"Not supported class type for Message implementation");
}
}
}
Finally, you'd have an utility class (BasicUtils.java):
private static final ObjectMapper MAPPER;
// following good software practices, utils can not have constructors
private BasicUtils() {}
static {
final SimpleModule module = new SimpleModule();
MAPPER = new ObjectMapper();
module.addDeserializer(Basic.class, new BasicDeserializer());
MAPPER.registerModule(module);
}
public static String buildJSONFromMessage(final Basic message)
throws JsonProcessingException {
return MAPPER.writeValueAsString(message);
}
public static Basic buildMessageFromJSON(final String jsonMessage)
throws IOException {
return MAPPER.readValue(jsonMessage, Basic.class);
}
For testing:
#Test
public void testJsonToChildA() throws IOException {
String message = "{\"propertyUniqueForThisClass\": \"ChildAValue\"}";
Basic basic = BasicUtils.buildMessageFromJSON(message);
assertNotNull(basic);
assertTrue(basic instanceof ChildA);
System.out.println(basic);
}
#Test
public void testJsonToChildB() throws IOException {
String message = "{\"childBUniqueProperty\": \"ChildBValue\"}";
Basic basic = BasicUtils.buildMessageFromJSON(message);
assertNotNull(basic);
assertTrue(basic instanceof ChildB);
System.out.println(basic);
}
The source code can be found on: https://github.com/darkstar-mx/jsondeserializer
I find not exactly solution but a workaround. I used custom response class ChildAResponse and passed it to ObjectMapper.readValue() method.
class ChildAResponse extends Response<ChildA> {}
// deserialization
HTTPClient.objectMapper.readValue(
response,
ChildAResponse.class
)
So JsonTypeInfo and JsonSubTypes annotations on the interface are no longer needed.

#JsonDeserialize doesn't work when I put it above the method

I'm trying to custom deserialize incoming json to the field, but #JsonDeserialize is not working wheh I put it above the setter of the field I want to deserialize.
public class MyEntity {
#JsonProperty("identifier")
private String identifier;
#JsonDeserialize(using = IdentifierDeserializer.class)
public void setIdentifier(String identifier)
{
this.identifier = identifier;
}
}
....
public class IdentifierDeserializer
extends JsonDeserializer<String>
{
#Override
public String deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException
{
ObjectCodec oc = jp.getCodec();
JsonNode node = oc.readTree(jp);
final JsonNode entity = node.get("entity");
return entity.get("id").asText();
}
}
Im doing this via _restTemplate httpMessageConverter flow, just standar API calls, and no, it does not throw any exceptions, it just not invoked, because I this this with debug and my breakpoints left untouched.
HttpEntity<T> httpEntity = new HttpEntity<>(entity, httpHeaders());
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
jsonConverter.setSupportedMediaTypes(Lists.newArrayList(MediaType.APPLICATION_JSON_UTF8));
_restTemplate.getMessageConverters().add(jsonConverter);
return _restTemplate.exchange(url, method, httpEntity, MyEntity.class).getBody();
But #JsonDeserialize does work when I put it above the class. So what's the problem guys? Thanks.

jackson: mapping some fields of json to inner fields of class

I want to map some fields of json to inner fields of a class. e.g
{
values:[{
"name":"Abc",
"age":18,
"street":"test",
"postalcoad":"1231412"
},
{
"name":"ccvb",
"age":20,
"street":"test2",
"postalcoad":"123"
}
]}
Following i my java class
#JsonIgnoreProperties(ignoreUnknown = true)
public class Customer{
#JsonProperty("name")
private string name;
#JsonProperty("age")
private int age;
private Address address;
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class Address{
#JsonProperty("street")
private string street;
#JsonProperty("postalcode")
private string postalcode;
}
ObjectMapper mapper = new ObjectMapper();
Customer[] c = mapper.readValue(mapper.readTree(json).get("values").toString(), Customer[].class);
It returns me Customer object without Address. Any idea how can i create Address object from this json.
One of the options is to use #JsonCreator annotation:
#JsonCreator
public Customer(
#JsonProperty("name") String name,
#JsonProperty("age") int age,
#JsonProperty("street") String street,
#JsonProperty("postalcode") String postalcode
) {
this.name = name;
this.age = age;
this.address = new Address();
this.address.street = street;
this.address.postalcode = postalcode;
}
Second option is create custom deserializer and bind your class with deserializer using #JsonDeserialize annotation
#JsonDeserialize(using = CustomerDeserializer.class)
public static class Customer{
....
}
public class CustomerDeserializer extends StdDeserializer<Customer> {
public CustomerDeserializer() {
super(Customer.class);
}
#Override
public Customer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
Customer customer = new Customer();
JsonNode treeNode = p.readValueAsTree();
if (treeNode == null) {
return null;
}
customer.setName(treeNode.get("name").asText());
customer.setAge(treeNode.get("age").asInt());
Address address = new Address();
address.setStreet(treeNode.get("street").asText());
address.setPostalcode(treeNode.get("postalcode").asText());
customer.setAddress(address);
return customer;
}
}
As third option, you can use #JsonAnySetter with some kind of post construct processing:
public interface PostConstruct {
void postConstruct();
}
public class Customer implements PostConstruct {
//mapping
private Map<String, Object> additionalFields = new HashMap<>();
#JsonAnySetter
public void setAdditionalValue(String key, Object value) {
additionalFields.put(key, value);
}
#Override
public void postConstruct() {
address = new Address();
address.setStreet(String.valueOf(additionalFields.get("street")));
address.setPostalcode(String.valueOf(additionalFields.get("postalcode")));
}
}
public static class PostConstructDeserializer extends DelegatingDeserializer {
private final JsonDeserializer<?> deserializer;
public PostConstructDeserializer(JsonDeserializer<?> deserializer) {
super(deserializer);
this.deserializer = deserializer;
}
#Override
protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegatee) {
return deserializer;
}
#Override
public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
Object result = _delegatee.deserialize(jp, ctxt);
if (result instanceof PostConstruct) {
((PostConstruct) result).postConstruct();
}
return result;
}
}
//using of post construct deserializer
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
#Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
BeanDescription beanDesc,
final JsonDeserializer<?> deserializer) {
return new PostConstructDeserializer(deserializer);
}
});
mapper.registerModule(module);
I would create a custom deserializer and inside of it call the default deserializer for Customer and then call the default deseriazlier for Address. Then you add the address to the customer object. This way they both look at the same json but you get two different objects out and you can connect them the way you want.
To call a standard deserializer from a custom deseriazlier see this answer: How do I call the default deserializer from a custom deserializer in Jackson.

Categories