Jackson and conditional expression trees

25 views
Skip to first unread message

Marcus Biel

unread,
May 1, 2023, 7:10:53 PM5/1/23
to jackson-user
Hello everyone,

I'm dealing with a project that has a complex setup, and I'm trying to simplify it. Our application allows users to establish Conditions or Filters in the UI. These Conditions/Filters are stored in a database and can be used to limit database queries using WHERE conditions. Interestingly, these filters can be built recursively from other filters.

Our current structure involves DTO classes marked with Jackson annotations, which build a hierarchy. This hierarchy is mapped into business objects, despite not having real business logic. These are later converted into database DTOs. The system is quite intricate as it spans across three layers - REST, Domain, and ORM Mapper. Additionally, the code, written two years ago by another developer, extensively uses Jackson Annotations, which increases the complexity and makes it difficult for me to understand.

Here are the two code snippets for the REST DTOs and the ORM Mapper:

REST DTOs:

java

@JsonTypeInfo(use = DEDUCTION) // No idea why DEDUCTION was used here but JsonTypeInfo.Id.NAME, below...
@JsonSubTypes({
        @Type(AndDto.class),
        @Type(OrDto.class),
        @Type(ConditionDto.class),
        @Type(FieldDto.class),
        @Type(LabelDto.class)
})
public interface ExpressionDto {
}

ORM Mapper:

java

@JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        property = "type")
@JsonSubTypes({
        @JsonSubTypes.Type(value = AndDbDto.class, name = "and"),
        @JsonSubTypes.Type(value = OrDbDto.class, name = "or"),
        @JsonSubTypes.Type(value = ConditionDbDto.class, name = "condition"),
        @JsonSubTypes.Type(value = FieldDbDto.class, name = "field"),
        @JsonSubTypes.Type(value = LabelDBDto.class, name = "label")
})
public interface ExpressionDbData {
}

I'm faced with the task of integrating about 15 more logical operators into the model, on top of the two already present - Equals for Field and Contains for Labels. Incorporating these operators would significantly ramp up the complexity of the existing model.

Each field type is linked to a data type - Text, Number, or Date. Each data type should support its specific logical comparison operators:

    TEXT: EQUAL, EQUAL_IGNORE_CASE, NOT_EQUAL, START_WITH, ENDS_WITH, CONTAINS
    NUMBER: EQUAL, NOT_EQUAL, GREATER, SMALLER, GREAT_EQUAL, SMALLER_EQUAL
    DATE: EQUAL, BEFORE, AFTER, BEFORE_OR_EQUAL, EQUAL_OR_AFTER

I'm searching for a more straightforward yet valid method to accept these conditions/filters from the frontend, store them in the database, and later apply them to database queries. At present, we're achieving this with a custom JPA CriteriaBuilder.

Could using a custom Serializer / Deserializer or maybe an AttributeConverter be helpful in this case?
Something like this maybe:
    @Override
    public void serialize(AndExpression value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        gen.writeStartObject();
        gen.writeStringField("type", "and");
        gen.writeFieldName("expressions");
        gen.writeStartArray();
        for (Expression expression : value.getExpressions()) {
            gen.writeObject(expression);
        }
        gen.writeEndArray();
        gen.writeEndObject();
    }
}

public class AndExpressionDeserializer extends StdDeserializer<AndExpression> {
    public AndExpressionDeserializer() {
        super(AndExpression.class);
    }

    @Override
    public AndExpression deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        JsonNode node = p.getCodec().readTree(p);
        JsonNode expressionsNode = node.get("expressions");
        List<Expression> expressions = new ArrayList<>();
        for (JsonNode expressionNode : expressionsNode) {
            Expression expression = p.getCodec().treeToValue(expressionNode, Expression.class);
            expressions.add(expression);
        }
        return new AndExpression(expressions);
    }
}

I'm not sure, and I'm open to suggestions. Currently, I'm using the latest versions of Java and Frameworks - Java 20, Hibernate 6.2, and Quarkus 3.x,
so, for instance, I could use Sealed classes if it would help...

Thanks in advance,


Marcus
p.s.: Posted this again as my earlier message did not appear. Excuse me if this appears as a duplicate.
Reply all
Reply to author
Forward
0 new messages