66

I am trying to write a custom JSON deserializer in Spring. I want to use default serializer for most part of fields and use a custom deserializer for few properties. Is it possible? I am trying this way because, most part of properties are values, so for these I can let Jackson use default deserializer; but few properties are references, so in the custom deserializer I have to query a database for reference name and get reference value from database.

I'll show some code if needed.

rajadilipkolli
  • 3,475
  • 2
  • 26
  • 49
gc5
  • 9,468
  • 24
  • 90
  • 151

4 Answers4

101

I've searched a lot and the best way I've found so far is on this article:

Class to serialize

package net.sghill.example;

import net.sghill.example.UserDeserializer
import net.sghill.example.UserSerializer
import org.codehaus.jackson.map.annotate.JsonDeserialize;
import org.codehaus.jackson.map.annotate.JsonSerialize;

@JsonDeserialize(using = UserDeserializer.class)
public class User {
    private ObjectId id;
    private String   username;
    private String   password;

    public User(ObjectId id, String username, String password) {
        this.id = id;
        this.username = username;
        this.password = password;
    }

    public ObjectId getId()       { return id; }
    public String   getUsername() { return username; }
    public String   getPassword() { return password; }
}

Deserializer class

package net.sghill.example;

import net.sghill.example.User;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.ObjectCodec;
import org.codehaus.jackson.map.DeserializationContext;
import org.codehaus.jackson.map.JsonDeserializer;

import java.io.IOException;

public class UserDeserializer extends JsonDeserializer<User> {

    @Override
    public User deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
        ObjectCodec oc = jsonParser.getCodec();
        JsonNode node = oc.readTree(jsonParser);
        return new User(null, node.get("username").getTextValue(), node.get("password").getTextValue());
    }
}

Edit: Alternatively you can look at this article which uses new versions of com.fasterxml.jackson.databind.JsonDeserializer.

turoni
  • 1,345
  • 1
  • 18
  • 37
gc5
  • 9,468
  • 24
  • 90
  • 151
11

I was trying to @Autowire a Spring-managed service into my Deserializer. Somebody tipped me off to Jackson using the new operator when invoking the serializers/deserializers. This meant no auto-wiring of Jackson's instance of my Deserializer. Here's how I was able to @Autowire my service class into my Deserializer:

context.xml

<mvc:annotation-driven>
  <mvc:message-converters>
    <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
      <property name="objectMapper" ref="objectMapper" />
    </bean>
  </mvc:message-converters>
</mvc>
<bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
    <!-- Add deserializers that require autowiring -->
    <property name="deserializersByType">
        <map key-type="java.lang.Class">
            <entry key="com.acme.Anchor">
                <bean class="com.acme.AnchorDeserializer" />
            </entry>
        </map>
    </property>
</bean>

Now that my Deserializer is a Spring-managed bean, auto-wiring works!

AnchorDeserializer.java

public class AnchorDeserializer extends JsonDeserializer<Anchor> {
    @Autowired
    private AnchorService anchorService;
    public Anchor deserialize(JsonParser parser, DeserializationContext context)
             throws IOException, JsonProcessingException {
        // Do stuff
    }
}

AnchorService.java

@Service
public class AnchorService {}

Update: While my original answer worked for me back when I wrote this, @xi.lin's response is exactly what is needed. Nice find!

Beez
  • 2,081
  • 1
  • 20
  • 23
  • 1
    Your solution not helped to me. I solved this problem using **[SpringBeanAutowiringSupport](http://docs.spring.io/spring/docs/2.5.6/api/org/springframework/web/context/support/SpringBeanAutowiringSupport.html)**. – Peter Jurkovic Feb 04 '14 at 20:46
  • 4
    I think you can also see this [refer](http://www.runningasroot.com/blog/2012/05/02/autowiring-jackson-deserializers-in-spring/) that use `HandlerInstantiator` to do this. – xi.lin Jan 20 '15 at 03:51
  • It's interesting that this approach works for some and not for others. The approach used by @PeterJurkovič did not work for me, but this one does. – Vivin Paliath Apr 15 '15 at 22:11
  • Perhaps it's been fixed in later versions. I don't remember which Spring version I was using when I wrote this, but that would have been very important to note. – Beez Apr 16 '15 at 15:16
  • If you want to use it without XML config just look on my [answer](https://stackoverflow.com/a/48421764/325868) – LeO Jan 24 '18 at 11:48
1

With Spring MVC 4.2.1.RELEASE, you need to use the new Jackson2 dependencies as below for the Deserializer to work.

Dont use this

<dependency>  
            <groupId>org.codehaus.jackson</groupId>  
            <artifactId>jackson-mapper-asl</artifactId>  
            <version>1.9.12</version>  
        </dependency>  

Use this instead.

<dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.2.2</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.2.2</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.2.2</version>
        </dependency>  

Also use com.fasterxml.jackson.databind.JsonDeserializer and com.fasterxml.jackson.databind.annotation.JsonDeserialize for the deserialization and not the classes from org.codehaus.jackson

moffeltje
  • 4,521
  • 4
  • 33
  • 57
VimalKumar
  • 1,611
  • 1
  • 14
  • 11
0
  1. If you wanna override default deserialiser for specific properties, you can mark the properties and the deserialiser you want to use, eg:

--

// root json object
public class Example {
  private String name;
  private int value;
  // You will implement your own deserilizer/Serializer for the field below//
  @JsonSerialize(converter = AddressSerializer.class)
  @JsonDeserialize(converter = AddressDeserializer.class)
  private String address;
}

Here is a complete example.

  1. If you want to use non spring managed object mapper in Spring application context and configure the serialisers/deserialisers to use spring managed services to query the database, that can be achieved by telling Jackson to use Spring Handler instantiator to create instances of Deserialisers/Serialisers.

In your application context config, create ObjectMapper bean with SpringHandlerInstantiator eg:

@Autowired
ApplicationContext applicationContext;

@Bean    
public ObjectMapper objectMapper(){
    Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
    builder.handlerInstantiator(handlerInstantiator());
    // add additional configs, etc.. here if needed
    return builder.build();
}

@Bean
public HandlerInstantiator handlerInstantiator(){
    return new SpringHandlerInstantiator(applicationContext.getAutowireCapableBeanFactory());
}

Then you can @Autowire above objectMapper to deserialise jsons:

@Autowired
ObjectMapper objectMapper;

public Something readSomething(...){
    ...
    Something st = objectMapper.readValue(json, Something.class);
    ...
    return st;
}

Regardless of the deserialiser you want to use, ie: field or class, you can use spring context in the deserialiser, meaning that you can @Autowire your services or any spring bean managed by the same ApplicationContext into it.

public class MyCustomDeserialiser extends ..{
      
    @Autowired;
    MyService service;

    @AutoWired
    SomeProperties properties;

    @Override
    public MyValue deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
    ....
    }
    ...
}

Additionally, you can find an example of Jackson deserialiser here.

Dhanushka
  • 216
  • 3
  • 7