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