Expression Tree

35 views
Skip to first unread message

Marcus Biel

unread,
May 1, 2023, 7:11:02 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 DTO sample:

@JsonTypeInfo(use = DEDUCTION)
@JsonSubTypes({
        @Type(AndDto.class),
        @Type(OrDto.class),
        @Type(ConditionDto.class),
        @Type(FieldDto.class),
        @Type(LabelDto.class)
})
public interface ExpressionDto {
}

ORM layer DTO sample of basically the same interface:
@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? I'm not sure, and I'm open to suggestions. Currently, the project is using the latest versions of Java and Frameworks - Java 20, Hibernate 6.2, and Quarkus 3.x. I could leverage Sealed classes if Jackson supports them.

Also, could someone clarify the difference between the use of DEDUCTION in the REST layer and JsonTypeInfo.Id.NAME in the database layer version of the same class?

Thanks a lot in advance!

Marcus

Tatu Saloranta

unread,
May 3, 2023, 11:51:30 PM5/3/23
to jackso...@googlegroups.com
On Mon, May 1, 2023 at 4:11 PM Marcus Biel <indu...@gmail.com> wrote:
>
> 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.

While I haven't built anything quite like this myself, I have seen
similar use cases, for what it is worth.
So maybe others with direct experience can share their learnings.

>
> 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.

Yes, that sounds like a complex beast.
Custom deserializers can be tricky: obviously you can do anything and
everything, but at the same time if they need to interoperate with
standard Jackson (de)serializers there's quite a bit more work to do.

Another approach that can be helpful is 2-pass (or multi-pass), in
which a JsonNode-based model is bound from/to JSON, modified, then
used as source for ObjectMapper. This can be used for all kinds of
pre-/post-processing.

>
> Also, could someone clarify the difference between the use of DEDUCTION in the REST layer and JsonTypeInfo.Id.NAME in the database layer version of the same class?

Ok, so: DEDUCTION basically means that no explicit Type Id (property,
or wrapper Object/Array) is used; type is deduced solely from the
existence of specific property names. Value types then must have,
each, at least one unique property name (something no other type has).
On serialization no type id is written, only regular properties.

The other choice used is to add (and use) specific Object Property
(here "type") to contain Type Id; and as id use logical, annotated
"name" (instead of Java class, the main alternative).
Type Id is written as Object metadata in addition to regular
properties (or in general, regular value representation).

I must say I am not sure why DEDUCTION was used here: I think it's
more common to use explicit Type Ids for both.

-+ Tatu +-

>
> Thanks a lot in advance!
>
> Marcus
>
> --
> You received this message because you are subscribed to the Google Groups "jackson-user" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to jackson-user...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/jackson-user/14120539-f3f0-4458-b206-c2b31002a9e2n%40googlegroups.com.

Joo Hyuk Kim (김주혁)

unread,
May 4, 2023, 2:04:57 AM5/4/23
to jackson-user

I am doing quite similar stuff at my current day job 👍🏻

Hope My suggestions can help.


1# “NO” to using a custom de/serializer.
 I started using @JsonTypeInfo at work to avoid exactly that.
 Having to write custom de/serializers for every DTO class.

2# Use Id.Name instead.

 I suggest to change DEDUCTION based handling on REST layer to JsonTypeInfo.Id.NAME based. 

 This will require change in REST API specification, but in the long term it will be easier to debug/maintain.

 Imagine how much a troublesome it would be for a server to having to  “assume” every time what the client intends to do with the request!


3# Maintain `@JsonSubTypes.Type.name` attribute as constants.

 Simple coding convention, maintain a variable in one place.

 Since @JsonTypeInfo provides polymorphic type handling which is already complex, hard-coding type id will get back to you soon. It took me only a few days … :(

 Take below code for example

# AS-IS

```java
@JsonSubTypes({

        @JsonSubTypes.Type(value = AndDbDto.class, name = AND_VALUE),

        @JsonSubTypes.Type(value = OrDbDto.class, name = OR_VALUE)

})

```

# TO-BE

```java
@JsonSubTypes({

        @JsonSubTypes.Type(value = AndDbDto.class, name = AND_VALUE),

        @JsonSubTypes.Type(value = OrDbDto.class, name = OR_VALUE),

        @JsonSubTypes.Type(value = ConditionDbDto.class, name = CONDITION_VALUE)

})

public enum DbExpressions {

      AND, OR, CONITION

     // Constants

     public static final String AND_VALUE = “and”;

     public static final String OR_VALUE = “or”;

      public static final String CONDITION_VALUE = “condition”;

}

```
Reply all
Reply to author
Forward
0 new messages