Passing objects with class inheritance causes wrong deserialisation

Hi, TLDR we have implemented the following (the comments contain the reasoning). Maybe it was not necessary and there exists a more elegant solution.


/**
 * Because Temporal stores data serialised without having access to the class definitions, it stores type
 * information based on the declared type. This applies to all types of methods: Workflows, Signals, Queries,
 * Activities. In case the objects passed are descendants of that type, this information is lost. Then during
 * deserialisation critical errors of type UnknownProperty will cause failure.
 * To overcome that problem we need to manually store the exact type and then deserialise to that type. To trick
 * the serialisation layer data is first transformed to a Tree which is generic and will be deserialised exactly,
 * and then we convert back to the right object by using the type information stored in a separate top-level
 * attribute.
 */
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class WrappedPayload implements Serializable {
    static ObjectMapper MAPPER = new ObjectMapper(); // must be static

    static {
        MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        MAPPER.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        MAPPER.registerModule(new JavaTimeModule());
    }

    String type;
    JsonNode data;

    public static WrappedPayload of(MessagePayload data) {
        return new WrappedPayload(
                data.getClass().getTypeName(),
                MAPPER.valueToTree(data));
    }

    public void replace(MessagePayload data) {
        if (!data.getClass().getTypeName().equals(type)) {
            throw new IllegalArgumentException("The type wrapped is immutable");
        }
        this.data = MAPPER.valueToTree(data);
    }

    public MessagePayload getData() {
        try {
            return (MessagePayload) MAPPER.treeToValue(data, Class.forName(type));
        } catch (ClassNotFoundException | JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    public String dataAsString() {
        return data.toString();
    }

}

Wonder if it’s possible to do same with a custom payload converter instead (see sample here)?

If only thing needed is to add object mapper options you can do that and still use default JacksonJsonPayloadConverter, see sample here.

The custom converter would overcome critical errors when de-serialising but would fail to de-serialise to the right class. The bottom-level class is not transferred with the payload IMHO, it’s taken from the the declared type at the worker level.

Ok think I understand it, then having a wrapper input is ok, we have similar wrapper in dsl sample as well.