1

I'm trying to serialize and deserialize a class which contains a field of the type Object. When I assign a variable to this field, for instance an Integer, then the serialized version of the class does not contain any type information,regardless of using the JsonTypeInfo annotation. I'm not sure how to continue so if anybody can point me into the right direction

The full code can be found on my github here: https://github.com/Notedop/BusinessRuleValidator

I have already checked this SO: java jackson keep type information after serialize if variable of type Object however this solution is not working for me as the objectToEvaluate field still does not contain type information after Serialization.

Field objectToEvaluate is of type Object but has an Integer type assigned to it. This is not visible in the serialisation and causes issues when deserializing. In theory it should be able to have any type assigned to it and be able to serialize and deserialize propertly without loosing the original type information.

<BusinessRuleSet _class="nl.rvh.rulevalidation.BusinessRuleSet">
<name>Check Golden Cross</name>
<operator>AND</operator>
<successResultApplicator _class="nl.rvh.rulevalidation.LogApplicator">
    <parameters>
        <log>the result is SUCCESS!</log>
    </parameters>
</successResultApplicator>
<failResultApplicator _class="nl.rvh.rulevalidation.LogApplicator">
    <parameters>
        <log>the result is SUCCESS!</log>
    </parameters>
</failResultApplicator>
<businessRules>
    <businessRules _class="nl.rvh.rulevalidation.MaGoldenCross">
        <comparisonOperator>GREATER_THAN</comparisonOperator>
        <objectToEvaluate>2.0</objectToEvaluate>
        <name>MaGoldenCross</name>
        <successResultApplicator _class="nl.rvh.rulevalidation.LogApplicator">
            <parameters>
                <log>the result is SUCCESS!</log>
            </parameters>
        </successResultApplicator>
        <failResultApplicator _class="nl.rvh.rulevalidation.LogApplicator">
            <parameters>
                <log>the result is SUCCESS!</log>
            </parameters>
        </failResultApplicator>
    </businessRules>
    <businessRules _class="nl.rvh.rulevalidation.MaGoldenCross">
        <comparisonOperator>GREATER_THAN</comparisonOperator>
        <objectToEvaluate>10000000</objectToEvaluate>
        <name>MaGoldenCross</name>
        <successResultApplicator _class="nl.rvh.rulevalidation.LogApplicator">
            <parameters>
                <log>the result is SUCCESS!</log>
            </parameters>
        </successResultApplicator>
        <failResultApplicator _class="nl.rvh.rulevalidation.LogApplicator">
            <parameters>
                <log>the result is SUCCESS!</log>
            </parameters>
        </failResultApplicator>
    </businessRules>
</businessRules>

I guess the serializing and deserializing kind-off works, as the value is assigned to the Field objectToEvaluate, however the type information is lost and after deserialization it's a String type stored as an Object instead of an Integer type stored as an Object.

There is a test case called serializeAndDeserializeRuleSet(). This test case creates a businessruleset, evaluates it, serializes it, de-serializes it and then tries to evaluate it again. The second time the evaluation runs, it fails because of a class cast exception as result of deserialization issue.

The class that's having issues is the abstract BusinessRule class. The field is called objectToEvaluate

    package nl.rvh.rulevalidation;
    
    import com.fasterxml.jackson.annotation.*;
    import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
    import nl.rvh.rulevalidation.enums.ComparisonOperator;
    
    import javax.print.DocFlavor;
    
    /**
     * Abstract business rule class. Extend this class to implement custom business rule.
     * The implementing class must contain a constructor having the @JsonCreator annotation
     * and JsonProperty annotation on the constructor parameters to assure proper serialization
     * and de-serialization.
     *
     * Implement the Evaluate method in the derived class. You can process the objectToEvaluate to your
     * liking and then use the comparisonOperator.compare() method to do the final evaluation
     *
     */
    @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "@class", visible = true)
    public abstract class BusinessRule extends Rule {
    
        @JacksonXmlProperty
        protected ComparisonOperator comparisonOperator;
        @JacksonXmlProperty
        @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "@class", visible = true)
        protected Object objectToEvaluate;
    
        @JsonCreator
        protected BusinessRule(@JsonProperty("comparisonOperator") ComparisonOperator comparisonOperator, @JsonProperty("objectToEvaluate") Object objectToEvaluate, @JsonProperty("name") String name) {
            super(name);
            this.objectToEvaluate = objectToEvaluate;
            this.comparisonOperator = comparisonOperator;
        }
//getters and setters and stuff here
        }

The derived class is as following:

package nl.rvh.rulevalidation;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import nl.rvh.rulevalidation.enums.ComparisonOperator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "@class", visible = true)
public class MaGoldenCross extends BusinessRule {

    private Logger log = LoggerFactory.getLogger(MaGoldenCross.class);

    @JsonCreator
    public MaGoldenCross(@JsonProperty("comparisonOperator") ComparisonOperator comparisonOperator, @JsonProperty("objectToEvaluate") Object expectedValue) {
        super(comparisonOperator, expectedValue, "MaGoldenCross");
    }

    @Override
    boolean evaluate(Object objectToEvaluate) {
        log.debug("Evaluating {} if {} is {} expected value {}", name, objectToEvaluate, comparisonOperator.getDescription(), this.objectToEvaluate);

        //do some stuff to the objectToEvaluate, or directly pass it for evaluation

        return comparisonOperator.compare(objectToEvaluate, getObjectToEvaluate());
    }

}

The test case

@Test
void serializeAndDeserializeRuleSet() throws JsonProcessingException {
    XmlMapper xmlMapper = new XmlMapper();
    getBusinessRuleSet().evaluate(6);
    String xml = xmlMapper.writeValueAsString(getBusinessRuleSet());
    log.debug(xml);
    BusinessRuleSet businessRuleSet = xmlMapper.readValue(xml, BusinessRuleSet.class);
    log.debug("{}", businessRuleSet);
    Assertions.assertTrue(businessRuleSet.evaluate(6)); // <---- FAILS as deserialized

}

private BusinessRuleSet getBusinessRuleSet() {
    BusinessRuleSet businessRules = new BusinessRuleSet("Check Golden Cross", LogicalOperator.AND);

    BusinessRule businessRule1 = new MaGoldenCross(GREATER_THAN, 2.0);
    BusinessRule businessRule2 = new MaGoldenCross(GREATER_THAN, 10000000);

    Map<String, Object> succesMap = new HashMap<>();
    succesMap.put("log", "the result is SUCCESS!");

    LogApplicator successLogApplicator = new LogApplicator(succesMap);
    LogApplicator failLogApplicator = new LogApplicator(succesMap);

    businessRule1.setSuccessResultApplicator(successLogApplicator);
    businessRule2.setSuccessResultApplicator(successLogApplicator);
    businessRule1.setFailResultApplicator(failLogApplicator);
    businessRule2.setFailResultApplicator(failLogApplicator);

    businessRules.addRule(businessRule1);
    businessRules.addRule(businessRule2);

    businessRules.setSuccessResultApplicator(successLogApplicator);
    businessRules.setFailResultApplicator(failLogApplicator);
    return businessRules;
}

UPDATE:

I've been trying but still unfortunately no luck with Jackson. As last resort i have started looking into different serialization libraries. xStream is able to perfectly serialize/deserialize a java.lang.Object and still remembering it's subtype information.

public class SimplePojo {

    Object canBeAnySubType;

    public Object getCanBeAnySubType() {
        return canBeAnySubType;
    }

    public void setCanBeAnySubType(Object canBeAnySubType) {
        this.canBeAnySubType = canBeAnySubType;
    }
}



    @Test
    void testDamnPojoWithXstream() {


        SimplePojo pojo = new SimplePojo();
        pojo.setCanBeAnySubType(new Integer(1));

        XStream xstream = new XStream(new StaxDriver());
        String xml = xstream.toXML(pojo);
        log.debug(xml);

        SimplePojo deserPojo =  (SimplePojo) xstream.fromXML(xml);
        if (deserPojo.getCanBeAnySubType() instanceof Integer)
            log.debug("success");

    }

Output

2021-06-08 18:40:23:228 +0200 [main] DEBUG nl.rvh.rulevalidation.TestPojo - <?xml version='1.0' encoding='UTF-8'?><nl.rvh.rulevalidation.SimplePojo><canBeAnySubType class="int">1</canBeAnySubType></nl.rvh.rulevalidation.SimplePojo>
Security framework of XStream not explicitly initialized, using predefined black list on your own risk.
2021-06-08 18:40:23:251 +0200 [main] DEBUG nl.rvh.rulevalidation.TestPojo - success
Notedop
  • 81
  • 1
  • 8
  • I dont see class cast exception https://gist.github.com/ozkanpakdil/89221de2cc6c40765ea565b911c5a528 ? – ozkanpakdil Jun 16 '21 at 10:56
  • Hi @özkanpakdil , Thank you for taking the time to look into this. The latest code branch on github is updated to utilize xStream instead of Jackson. That's the reason why you don't see the exception anymore. In case you still want to verify how to do it with jackson, you could checkout [this commit](https://github.com/Notedop/BusinessRuleValidator/commit/28ec86cffb2ed1bd127cd822f0555d6f38236f27). I'm happy to accept any answer that has a solution for Jackson, allthough I have already migrated to xStream. – Notedop Jul 27 '21 at 10:32

0 Answers0