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.