Serializing and Deserializing Lambda with Jackson Serializing and Deserializing Lambda with Jackson json json

Serializing and Deserializing Lambda with Jackson


Jackson was created to keep object state not behaviour. This is why it tries to serialise POJO's properties using getters, setters, etc. Serialising lambdas break this idea. Theres is no any property to serialise, only a method which should be invoked. Serialising raw lambda object is really bad idea and you should redesign your app to avoid uses cases like this.

In your case SerializableRunnable interface extends java.io.Serializable which gives one option - Java Serialisation. Using java.io.ObjectOutputStream we can serialise lambda object to byte array and serialise it in JSON payload using Base64 encoding. Jackson supports this scenario providing writeBinary and getBinaryValue methods.

Simple example could look like below:

import com.fasterxml.jackson.annotation.JsonCreator;import com.fasterxml.jackson.annotation.JsonProperty;import com.fasterxml.jackson.core.JsonGenerator;import com.fasterxml.jackson.core.JsonParser;import com.fasterxml.jackson.databind.DeserializationContext;import com.fasterxml.jackson.databind.JsonDeserializer;import com.fasterxml.jackson.databind.JsonSerializer;import com.fasterxml.jackson.databind.ObjectMapper;import com.fasterxml.jackson.databind.SerializationFeature;import com.fasterxml.jackson.databind.SerializerProvider;import com.fasterxml.jackson.databind.annotation.JsonDeserialize;import com.fasterxml.jackson.databind.annotation.JsonSerialize;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;public class JsonLambdaApp {    public static void main(String[] args) throws IOException {        ObjectMapper mapper = new ObjectMapper();        mapper.enable(SerializationFeature.INDENT_OUTPUT);        SerializableRunnable action = () -> System.out.println("Serializable!");        String json = mapper.writeValueAsString(new RuleMessage("1", action));        System.out.println(json);        RuleMessage ruleMessage = mapper.readValue(json, RuleMessage.class);        ruleMessage.getsRunnable().run();    }}@JsonSerialize(using = LambdaJsonSerializer.class)@JsonDeserialize(using = LambdaJsonDeserializer.class)interface SerializableRunnable extends Runnable, Serializable {}class LambdaJsonSerializer extends JsonSerializer<SerializableRunnable> {    @Override    public void serialize(SerializableRunnable value, JsonGenerator gen, SerializerProvider serializers) throws IOException {        try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();             ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream)) {            outputStream.writeObject(value);            gen.writeBinary(byteArrayOutputStream.toByteArray());        }    }}class LambdaJsonDeserializer extends JsonDeserializer<SerializableRunnable> {    @Override    public SerializableRunnable deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {        byte[] value = p.getBinaryValue();        try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(value);             ObjectInputStream inputStream = new ObjectInputStream(byteArrayInputStream)) {            return (SerializableRunnable) inputStream.readObject();        } catch (ClassNotFoundException e) {            throw new IOException(e);        }    }}class RuleMessage {    private String id;    private SerializableRunnable sRunnable;    @JsonCreator    public RuleMessage(@JsonProperty("id") String id, @JsonProperty("sRunnable") SerializableRunnable sRunnable) {        this.id = id;        this.sRunnable = sRunnable;    }    public String getId() {        return id;    }    public SerializableRunnable getsRunnable() {        return sRunnable;    }}

Above code prints JSON:

{  "id" : "1",  "sRunnable" : "rO0ABXNyACFqYXZhLmxhbmcuaW52b2tlLlNlcmlhbGl6ZWRMYW1iZGFvYdCULCk2hQIACkkADmltcGxNZXRob2RLaW5kWwAMY2FwdHVyZWRBcmdzdAATW0xqYXZhL2xhbmcvT2JqZWN0O0wADmNhcHR1cmluZ0NsYXNzdAARTGphdmEvbGFuZy9DbGFzcztMABhmdW5jdGlvbmFsSW50ZXJmYWNlQ2xhc3N0ABJMamF2YS9sYW5nL1N0cmluZztMAB1mdW5jdGlvbmFsSW50ZXJmYWNlTWV0aG9kTmFtZXEAfgADTAAiZnVuY3Rpb25hbEludGVyZmFjZU1ldGhvZFNpZ25hdHVyZXEAfgADTAAJaW1wbENsYXNzcQB+AANMAA5pbXBsTWV0aG9kTmFtZXEAfgADTAATaW1wbE1ldGhvZFNpZ25hdHVyZXEAfgADTAAWaW5zdGFudGlhdGVkTWV0aG9kVHlwZXEAfgADeHAAAAAGdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAAHZyABxjb20uY2Vsb3hpdHkuSnNvblR5cGVJbmZvQXBwAAAAAAAAAAAAAAB4cHQAIWNvbS9jZWxveGl0eS9TZXJpYWxpemFibGVSdW5uYWJsZXQAA3J1bnQAAygpVnQAHGNvbS9jZWxveGl0eS9Kc29uVHlwZUluZm9BcHB0ABZsYW1iZGEkbWFpbiQ1YzRiNmEwOCQxcQB+AAtxAH4ACw=="}

and lambda:

Serializable!

See also:


First, in RuleMessage you have to either create getters / setters or make the fields public in order to provide Jackson access to the fields.

Your code then prints something like this:

{"@class":"RuleMessage","id":"1","sRunnable":{"@class":"RuleMessage$$Lambda$20/0x0000000800b91c40"}}

This JSON document cannot be deserialized because RuleMessage has no default constructor and the lambda cannot be constructed.

Instead of the lambda, you could create a class:

  public class Runner implements SerializableRunnable {    @Override    public void run() {      System.out.println("Serializable!");    }  }

and construct your pojo like this:

new RuleMessage("1", new Runner())

The Jackson deserializer is now able to reconstruct the objects and execute the runner.