From 6c90a69e695b198f40924b2412e6bc7b0e88f09a Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Tue, 16 Jun 2026 09:45:50 +0200 Subject: [PATCH 1/6] [NAE-2443] Introduce FieldSelector for selective serialization of event outcomes Added `FieldSelector` and related classes (`FieldSelectorHolder`, `FieldSelectorInterceptor`, `ChangeRecordSerializer`, and `ObjectMapperConfiguration`) to enable selective field serialization based on request parameters. Integrated the interceptor into `WebConfig` for seamless request handling. --- .../configuration/web/config/WebConfig.java | 16 +++- .../serialization/ChangeRecordSerializer.java | 65 +++++++++++++++ .../engine/serialization/FieldSelector.java | 83 +++++++++++++++++++ .../serialization/FieldSelectorHolder.java | 14 ++++ .../FieldSelectorInterceptor.java | 25 ++++++ .../ObjectMapperConfiguration.java | 17 ++++ 6 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 application-engine/src/main/java/com/netgrif/application/engine/serialization/ChangeRecordSerializer.java create mode 100644 application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelector.java create mode 100644 application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelectorHolder.java create mode 100644 application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelectorInterceptor.java create mode 100644 application-engine/src/main/java/com/netgrif/application/engine/serialization/ObjectMapperConfiguration.java diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/web/config/WebConfig.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/web/config/WebConfig.java index a85a1850106..bfee7865522 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/web/config/WebConfig.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/web/config/WebConfig.java @@ -1,13 +1,22 @@ package com.netgrif.application.engine.configuration.web.config; import com.netgrif.application.engine.configuration.web.filter.TrailingSlashRedirectFilter; +import com.netgrif.application.engine.serialization.FieldSelectorInterceptor; import jakarta.servlet.Filter; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration -public class WebConfig { +public class WebConfig implements WebMvcConfigurer { + + private final FieldSelectorInterceptor interceptor; + + public WebConfig(FieldSelectorInterceptor interceptor) { + this.interceptor = interceptor; + } @Bean public Filter trailingSlashRedirectFilter() { @@ -21,4 +30,9 @@ public FilterRegistrationBean trailingSlashFilter() { registrationBean.addUrlPatterns("/*"); return registrationBean; } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(interceptor); + } } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/serialization/ChangeRecordSerializer.java b/application-engine/src/main/java/com/netgrif/application/engine/serialization/ChangeRecordSerializer.java new file mode 100644 index 00000000000..a3a825b6e0e --- /dev/null +++ b/application-engine/src/main/java/com/netgrif/application/engine/serialization/ChangeRecordSerializer.java @@ -0,0 +1,65 @@ +package com.netgrif.application.engine.serialization; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.netgrif.application.engine.workflow.web.responsebodies.eventoutcomes.LocalisedGetDataEventOutcome; +import com.netgrif.application.engine.workflow.web.responsebodies.eventoutcomes.LocalisedGetDataGroupsEventOutcome; +import com.netgrif.application.engine.workflow.web.responsebodies.eventoutcomes.LocalisedSetDataEventOutcome; +import com.netgrif.application.engine.workflow.web.responsebodies.eventoutcomes.base.LocalisedCaseEventOutcome; +import com.netgrif.application.engine.workflow.web.responsebodies.eventoutcomes.base.LocalisedEventOutcome; +import com.netgrif.application.engine.workflow.web.responsebodies.eventoutcomes.base.LocalisedPetriNetEventOutcome; +import com.netgrif.application.engine.workflow.web.responsebodies.eventoutcomes.base.LocalisedTaskEventOutcome; + +import java.io.IOException; + +public class ChangeRecordSerializer extends JsonSerializer { + + private final FieldSelectorHolder selectorHolder; + + public ChangeRecordSerializer(FieldSelectorHolder selectorHolder) { + this.selectorHolder = selectorHolder; + } + + @Override + public void serialize(LocalisedEventOutcome outcome, JsonGenerator gen, SerializerProvider provider) + throws IOException { + gen.writeStartObject(); + FieldSelector selector = selectorHolder.getSelector(); + + if (selector.includes("message") && outcome.getMessage() != null) { + gen.writeObjectField("message", outcome.getMessage()); + } + if (selector.includes("frontActions") && outcome.getFrontActions() != null) { + gen.writeObjectField("frontActions", outcome.getFrontActions()); + } + if (selector.includes("outcomes") + && outcome.getOutcomes() != null) { + gen.writeArrayFieldStart("outcomes"); + for (LocalisedEventOutcome child : outcome.getOutcomes()) { + provider.defaultSerializeValue(child, gen); + } + gen.writeEndArray(); + } + if (outcome instanceof LocalisedPetriNetEventOutcome specificOutcome && selector.includes("net") && specificOutcome.getNet() != null) { + gen.writeObjectField("net", specificOutcome.getNet()); + } + if (outcome instanceof LocalisedCaseEventOutcome specificOutcome && selector.includes("case") && specificOutcome.getaCase() != null) { + gen.writeObjectField("aCase", specificOutcome.getaCase()); + } + if (outcome instanceof LocalisedTaskEventOutcome specificOutcome && selector.includes("task") && specificOutcome.getTask() != null) { + gen.writeObjectField("task", specificOutcome.getaCase()); + } + if (outcome instanceof LocalisedGetDataEventOutcome specificOutcome && selector.includes("data") && specificOutcome.getData() != null) { + gen.writeObjectField("data", specificOutcome.getData()); + } + if (outcome instanceof LocalisedGetDataGroupsEventOutcome specificOutcome && selector.includes("data") && specificOutcome.getData() != null) { + gen.writeObjectField("data", specificOutcome.getData()); + } + if (outcome instanceof LocalisedSetDataEventOutcome specificOutcome && selector.includes("changedFields") && specificOutcome.getChangedFields() != null) { + gen.writeObjectField("changedFields", specificOutcome.getChangedFields()); + } + + gen.writeEndObject(); + } +} diff --git a/application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelector.java b/application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelector.java new file mode 100644 index 00000000000..cc36f855941 --- /dev/null +++ b/application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelector.java @@ -0,0 +1,83 @@ +package com.netgrif.application.engine.serialization; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public final class FieldSelector { + private final Set fields; + private final Map nested; + + private FieldSelector() { + this(null, null); + } + + private FieldSelector(Set fields, Map nested) { + this.fields = fields; + this.nested = nested; + } + + public static FieldSelector parse(String spec) { + Set fields = new HashSet<>(); + Map nested = new HashMap<>(); + + if (spec == null || spec.isBlank()) { + return new FieldSelector(); + } + + int i = 0; + while (i < spec.length()) { + int commaIdx = nextCommaOrEnd(spec, i); + int parenIdx = spec.indexOf('(', i); + + if (parenIdx != -1 && parenIdx < commaIdx) { + String name = spec.substring(i, parenIdx); + int closeParen = findMatchingParen(spec, parenIdx); + String inner = spec.substring(parenIdx + 1, closeParen); + nested.put(name, parse(inner)); + i = closeParen + 1; + if (i < spec.length() && spec.charAt(i) == ',') i++; + } else { + fields.add(spec.substring(i, commaIdx)); + i = commaIdx + 1; + } + } + return new FieldSelector(fields, nested); + } + + public boolean includes(String field) { + return fields == null || fields.contains(field); + } + + public FieldSelector nested(String field) { + if (nested == null || !nested.containsKey(field)) { + return new FieldSelector(null, null); // no restriction on nested level either + } + return nested.get(field); + } + + private static int nextCommaOrEnd(String spec, int from) { + int depth = 0; + for (int i = from; i < spec.length(); i++) { + char c = spec.charAt(i); + if (c == '(') depth++; + else if (c == ')') depth--; + else if (c == ',' && depth == 0) return i; + } + return spec.length(); + } + + private static int findMatchingParen(String spec, int openIdx) { + int depth = 0; + for (int i = openIdx; i < spec.length(); i++) { + char c = spec.charAt(i); + if (c == '(') depth++; + else if (c == ')') { + depth--; + if (depth == 0) return i; + } + } + throw new IllegalArgumentException("Unmatched '(' in fields selector: " + spec); + } +} diff --git a/application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelectorHolder.java b/application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelectorHolder.java new file mode 100644 index 00000000000..be8015ff91a --- /dev/null +++ b/application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelectorHolder.java @@ -0,0 +1,14 @@ +package com.netgrif.application.engine.serialization; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +@Setter +@Getter +@Component +@RequestScope +public class FieldSelectorHolder { + private FieldSelector selector = FieldSelector.parse(null); +} diff --git a/application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelectorInterceptor.java b/application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelectorInterceptor.java new file mode 100644 index 00000000000..afb1aced8a2 --- /dev/null +++ b/application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelectorInterceptor.java @@ -0,0 +1,25 @@ +package com.netgrif.application.engine.serialization; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +@Component +public class FieldSelectorInterceptor implements HandlerInterceptor { + + private final FieldSelectorHolder holder; + + public FieldSelectorInterceptor(FieldSelectorHolder holder) { + this.holder = holder; + } + + @Override + public boolean preHandle(HttpServletRequest request, @NonNull HttpServletResponse response, + @NonNull Object handler) { + String fields = request.getParameter("fields"); + holder.setSelector(FieldSelector.parse(fields)); + return true; + } +} diff --git a/application-engine/src/main/java/com/netgrif/application/engine/serialization/ObjectMapperConfiguration.java b/application-engine/src/main/java/com/netgrif/application/engine/serialization/ObjectMapperConfiguration.java new file mode 100644 index 00000000000..b4cbdbe5069 --- /dev/null +++ b/application-engine/src/main/java/com/netgrif/application/engine/serialization/ObjectMapperConfiguration.java @@ -0,0 +1,17 @@ +package com.netgrif.application.engine.serialization; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.netgrif.application.engine.workflow.web.responsebodies.eventoutcomes.base.LocalisedEventOutcome; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ObjectMapperConfiguration { + + @Bean + public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer(FieldSelectorHolder holder) { + return builder -> builder.serializationInclusion(JsonInclude.Include.NON_NULL) + .serializerByType(LocalisedEventOutcome.class, new ChangeRecordSerializer(holder)); + } +} From 0370b4899aa62dd7772bdf6f9fdc1abcbbe8512c Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Tue, 16 Jun 2026 10:50:46 +0200 Subject: [PATCH 2/6] [NAE-2451] Optimize and Reduce Outcome Size Introduced `DynamicFieldSerializer` to enable nested field selection during serialization. Updated `ChangeRecordSerializer` to leverage `DynamicFieldSerializer` for handling complex object structures with field selectors. Enhanced `FieldSelector` functionality to support nested field inclusion checks. --- .../serialization/ChangeRecordSerializer.java | 20 ++++-- .../serialization/DynamicFieldSerializer.java | 64 +++++++++++++++++++ .../engine/serialization/FieldSelector.java | 8 ++- 3 files changed, 85 insertions(+), 7 deletions(-) create mode 100644 application-engine/src/main/java/com/netgrif/application/engine/serialization/DynamicFieldSerializer.java diff --git a/application-engine/src/main/java/com/netgrif/application/engine/serialization/ChangeRecordSerializer.java b/application-engine/src/main/java/com/netgrif/application/engine/serialization/ChangeRecordSerializer.java index a3a825b6e0e..f3d87ea6af3 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/serialization/ChangeRecordSerializer.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/serialization/ChangeRecordSerializer.java @@ -16,9 +16,11 @@ public class ChangeRecordSerializer extends JsonSerializer { private final FieldSelectorHolder selectorHolder; + private final DynamicFieldSerializer dynamicSerializer; public ChangeRecordSerializer(FieldSelectorHolder selectorHolder) { this.selectorHolder = selectorHolder; + this.dynamicSerializer = new DynamicFieldSerializer(selectorHolder); } @Override @@ -42,22 +44,28 @@ public void serialize(LocalisedEventOutcome outcome, JsonGenerator gen, Serializ gen.writeEndArray(); } if (outcome instanceof LocalisedPetriNetEventOutcome specificOutcome && selector.includes("net") && specificOutcome.getNet() != null) { - gen.writeObjectField("net", specificOutcome.getNet()); + gen.writeFieldName("net"); + dynamicSerializer.serializeWithSelector(specificOutcome.getNet(), gen, provider, selector.nested("net")); } if (outcome instanceof LocalisedCaseEventOutcome specificOutcome && selector.includes("case") && specificOutcome.getaCase() != null) { - gen.writeObjectField("aCase", specificOutcome.getaCase()); + gen.writeFieldName("aCase"); + dynamicSerializer.serializeWithSelector(specificOutcome.getaCase(), gen, provider, selector.nested("case")); } if (outcome instanceof LocalisedTaskEventOutcome specificOutcome && selector.includes("task") && specificOutcome.getTask() != null) { - gen.writeObjectField("task", specificOutcome.getaCase()); + gen.writeFieldName("task"); + dynamicSerializer.serializeWithSelector(specificOutcome.getTask(), gen, provider, selector.nested("task")); } if (outcome instanceof LocalisedGetDataEventOutcome specificOutcome && selector.includes("data") && specificOutcome.getData() != null) { - gen.writeObjectField("data", specificOutcome.getData()); + gen.writeFieldName("data"); + dynamicSerializer.serializeWithSelector(specificOutcome.getData(), gen, provider, selector.nested("data")); } if (outcome instanceof LocalisedGetDataGroupsEventOutcome specificOutcome && selector.includes("data") && specificOutcome.getData() != null) { - gen.writeObjectField("data", specificOutcome.getData()); + gen.writeFieldName("data"); + dynamicSerializer.serializeWithSelector(specificOutcome.getData(), gen, provider, selector.nested("data")); } if (outcome instanceof LocalisedSetDataEventOutcome specificOutcome && selector.includes("changedFields") && specificOutcome.getChangedFields() != null) { - gen.writeObjectField("changedFields", specificOutcome.getChangedFields()); + gen.writeFieldName("changedFields"); + dynamicSerializer.serializeWithSelector(specificOutcome.getChangedFields(), gen, provider, selector.nested("changedFields")); } gen.writeEndObject(); diff --git a/application-engine/src/main/java/com/netgrif/application/engine/serialization/DynamicFieldSerializer.java b/application-engine/src/main/java/com/netgrif/application/engine/serialization/DynamicFieldSerializer.java new file mode 100644 index 00000000000..7fb1ba9ddb8 --- /dev/null +++ b/application-engine/src/main/java/com/netgrif/application/engine/serialization/DynamicFieldSerializer.java @@ -0,0 +1,64 @@ +package com.netgrif.application.engine.serialization; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.BeanDescription; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition; + +import java.io.IOException; + +public class DynamicFieldSerializer extends JsonSerializer { + + private final FieldSelectorHolder holder; + + public DynamicFieldSerializer(FieldSelectorHolder holder) { + this.holder = holder; + } + + @Override + public Class handledType() { + return Object.class; + } + + @Override + public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) + throws IOException { + serializeWithSelector(value, gen, provider, holder.getSelector()); + } + + public void serializeWithSelector(Object value, JsonGenerator gen, + SerializerProvider provider, FieldSelector selector) throws IOException { + + if (value == null) { + gen.writeNull(); + return; + } + + if (selector.includeAll()) { + provider.defaultSerializeValue(value, gen); + return; + } + + JavaType type = provider.constructType(value.getClass()); + BeanDescription desc = provider.getConfig().introspect(type); + + gen.writeStartObject(); + for (BeanPropertyDefinition prop : desc.findProperties()) { + String name = prop.getName(); + if (!selector.includes(name)) continue; + + Object propValue = prop.getAccessor().getValue(value); + gen.writeFieldName(name); + + FieldSelector nested = selector.nested(name); + if (nested.includeAll()) { + provider.defaultSerializeValue(propValue, gen); + } else { + serializeWithSelector(propValue, gen, provider, nested); + } + } + gen.writeEndObject(); + } +} diff --git a/application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelector.java b/application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelector.java index cc36f855941..39225e36289 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelector.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelector.java @@ -47,7 +47,13 @@ public static FieldSelector parse(String spec) { } public boolean includes(String field) { - return fields == null || fields.contains(field); + if (fields == null) return true; + if (fields.contains(field)) return true; + return nested != null && nested.containsKey(field); + } + + public boolean includeAll() { + return fields == null; } public FieldSelector nested(String field) { From 46779f5f0154c7194f3a6ea54ee565f299e29407 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Tue, 16 Jun 2026 10:56:04 +0200 Subject: [PATCH 3/6] [NAE-2451] Optimize and Reduce Outcome Size Renamed `ChangeRecordSerializer` to `LocalizedEventOutcomeSerializer` for better clarity and alignment with its purpose and usage in serializing `LocalisedEventOutcome`. Updated references in `ObjectMapperConfiguration`. --- ...rdSerializer.java => LocalizedEventOutcomeSerializer.java} | 4 ++-- .../engine/serialization/ObjectMapperConfiguration.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename application-engine/src/main/java/com/netgrif/application/engine/serialization/{ChangeRecordSerializer.java => LocalizedEventOutcomeSerializer.java} (95%) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/serialization/ChangeRecordSerializer.java b/application-engine/src/main/java/com/netgrif/application/engine/serialization/LocalizedEventOutcomeSerializer.java similarity index 95% rename from application-engine/src/main/java/com/netgrif/application/engine/serialization/ChangeRecordSerializer.java rename to application-engine/src/main/java/com/netgrif/application/engine/serialization/LocalizedEventOutcomeSerializer.java index f3d87ea6af3..ab8bb93cb3d 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/serialization/ChangeRecordSerializer.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/serialization/LocalizedEventOutcomeSerializer.java @@ -13,12 +13,12 @@ import java.io.IOException; -public class ChangeRecordSerializer extends JsonSerializer { +public class LocalizedEventOutcomeSerializer extends JsonSerializer { private final FieldSelectorHolder selectorHolder; private final DynamicFieldSerializer dynamicSerializer; - public ChangeRecordSerializer(FieldSelectorHolder selectorHolder) { + public LocalizedEventOutcomeSerializer(FieldSelectorHolder selectorHolder) { this.selectorHolder = selectorHolder; this.dynamicSerializer = new DynamicFieldSerializer(selectorHolder); } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/serialization/ObjectMapperConfiguration.java b/application-engine/src/main/java/com/netgrif/application/engine/serialization/ObjectMapperConfiguration.java index b4cbdbe5069..d6f16c23c20 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/serialization/ObjectMapperConfiguration.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/serialization/ObjectMapperConfiguration.java @@ -12,6 +12,6 @@ public class ObjectMapperConfiguration { @Bean public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer(FieldSelectorHolder holder) { return builder -> builder.serializationInclusion(JsonInclude.Include.NON_NULL) - .serializerByType(LocalisedEventOutcome.class, new ChangeRecordSerializer(holder)); + .serializerByType(LocalisedEventOutcome.class, new LocalizedEventOutcomeSerializer(holder)); } } From 92b498117c61711b018adb18a05b048f959cfa13 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Tue, 16 Jun 2026 11:41:25 +0200 Subject: [PATCH 4/6] [NAE-2451] Optimize and Reduce Outcome Size Enhanced JavaDocs for `FieldSelector`, `FieldSelectorHolder`, `FieldSelectorInterceptor`, `LocalizedEventOutcomeSerializer`, and `DynamicFieldSerializer`. Improved clarity and provided detailed explanations of class functionalities, methods, and usage scenarios to support selective field serialization. --- .../serialization/DynamicFieldSerializer.java | 75 +++++++++- .../engine/serialization/FieldSelector.java | 133 +++++++++++++++++- .../serialization/FieldSelectorHolder.java | 22 +++ .../FieldSelectorInterceptor.java | 3 +- .../LocalizedEventOutcomeSerializer.java | 48 +++++++ 5 files changed, 267 insertions(+), 14 deletions(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/serialization/DynamicFieldSerializer.java b/application-engine/src/main/java/com/netgrif/application/engine/serialization/DynamicFieldSerializer.java index 7fb1ba9ddb8..e48fa9de5d3 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/serialization/DynamicFieldSerializer.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/serialization/DynamicFieldSerializer.java @@ -9,25 +9,85 @@ import java.io.IOException; +/** + * Custom JSON serializer that provides selective field serialization based on a {@link FieldSelector} configuration. + *

+ * This serializer extends {@link JsonSerializer} to enable dynamic control over which fields of an object + * are included in the JSON output. It uses a {@link FieldSelectorHolder} to access the field selector + * configuration and applies it during serialization to filter object properties. + *

+ *

+ * The serializer supports: + *

    + *
  • Selective field inclusion based on field names
  • + *
  • Nested field selection for complex object hierarchies
  • + *
  • Fallback to default serialization when all fields are included
  • + *
  • Null-safe serialization
  • + *
+ *

+ *

+ * Example usage: + *

+ * FieldSelector selector = new FieldSelector();
+ * selector.include("name");
+ * selector.include("address.city");
+ *
+ * FieldSelectorHolder holder = new FieldSelectorHolder(selector);
+ * DynamicFieldSerializer serializer = new DynamicFieldSerializer(holder);
+ * 
+ *

+ * + * @see JsonSerializer + * @see FieldSelector + * @see FieldSelectorHolder + */ public class DynamicFieldSerializer extends JsonSerializer { + /** + * Holder containing the field selector used to determine which fields should be serialized. + */ private final FieldSelectorHolder holder; + /** + * Constructs a new DynamicFieldSerializer with the specified field selector holder. + * + * @param holder the field selector holder that provides the selector for filtering fields during serialization + */ public DynamicFieldSerializer(FieldSelectorHolder holder) { this.holder = holder; } - @Override - public Class handledType() { - return Object.class; - } - + /** + * Serializes the given object value to JSON using the field selector from the holder. + * This method delegates to {@link #serializeWithSelector(Object, JsonGenerator, SerializerProvider, FieldSelector)} + * with the selector obtained from the holder. + * + * @param value the object to serialize + * @param gen the JSON generator used for writing JSON content + * @param provider the serializer provider for accessing serializers + * @throws IOException if an I/O error occurs during serialization + */ @Override public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException { serializeWithSelector(value, gen, provider, holder.getSelector()); } + /** + * Serializes the given object value to JSON based on the provided field selector. + *

+ * This method performs selective serialization by including only the fields specified in the selector. + * If the selector includes all fields, the default serialization is performed. Otherwise, it introspects + * the object's bean properties and serializes only those included in the selector. Nested field selectors + * are applied recursively for complex object structures. + *

+ * + * @param value the object to serialize, it may be null + * @param gen the JSON generator used for writing JSON content + * @param provider the serializer provider for accessing serializers and configuration + * @param selector the field selector that determines which fields to include in serialization + * @throws IOException if an I/O error occurs during serialization + */ public void serializeWithSelector(Object value, JsonGenerator gen, SerializerProvider provider, FieldSelector selector) throws IOException { @@ -46,8 +106,11 @@ public void serializeWithSelector(Object value, JsonGenerator gen, gen.writeStartObject(); for (BeanPropertyDefinition prop : desc.findProperties()) { + String name = prop.getName(); - if (!selector.includes(name)) continue; + if (!selector.includes(name)) { + continue; + } Object propValue = prop.getAccessor().getValue(value); gen.writeFieldName(name); diff --git a/application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelector.java b/application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelector.java index 39225e36289..0d6cd7ffd1f 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelector.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelector.java @@ -5,19 +5,83 @@ import java.util.Map; import java.util.Set; +/** + * A utility class for parsing and managing field selection specifications in JSON serialization. + *

+ * This class enables selective field inclusion during serialization by parsing a field specification + * string that supports both simple field names and nested field selections using parentheses notation. + *

+ *

+ * Field specification format: + *

    + *
  • Simple fields: {@code "field1,field2,field3"}
  • + *
  • Nested fields: {@code "field1,field2(nestedField1,nestedField2),field3"}
  • + *
  • Deep nesting: {@code "field1(nested1(deepNested1,deepNested2))"}
  • + *
+ *

+ *

+ * When no specification is provided (null or blank), the selector allows all fields (unrestricted mode). + *

+ * + * @see LocalizedEventOutcomeSerializer + * @see DynamicFieldSerializer + * @see FieldSelectorHolder + */ + public final class FieldSelector { + /** + * Set of field names to include at the current level of serialization. + * When null, all fields are included (unrestricted mode). + */ private final Set fields; + + /** + * Map of field names to their corresponding nested {@link FieldSelector} instances. + * Enables hierarchical field selection for complex object structures. + * When null, no nested field restrictions are applied. + */ private final Map nested; + /** + * Creates a new FieldSelector in unrestricted mode. + *

+ * This constructor creates a selector that allows all fields to be included + * during serialization without any restrictions. + *

+ */ private FieldSelector() { this(null, null); } + /** + * Creates a new FieldSelector with specified field selections and nested selectors. + * + * @param fields the set of field names to include at this level, or null for unrestricted mode + * @param nested the map of field names to their nested selectors, or null if no nested selections + */ private FieldSelector(Set fields, Map nested) { this.fields = fields; this.nested = nested; } + /** + * Parses a field specification string and creates a corresponding FieldSelector. + *

+ * The parser supports: + *

    + *
  • Comma-separated field names: {@code "field1,field2,field3"}
  • + *
  • Nested field selections using parentheses: {@code "field1(nested1,nested2)"}
  • + *
  • Multiple levels of nesting: {@code "field1(nested1(deepNested1))"}
  • + *
+ *

+ *

+ * If the specification is null or blank, an unrestricted selector is returned that allows all fields. + *

+ * + * @param spec the field specification string to parse, may be null or blank + * @return a FieldSelector instance representing the parsed specification + * @throws IllegalArgumentException if the specification contains unmatched parentheses + */ public static FieldSelector parse(String spec) { Set fields = new HashSet<>(); Map nested = new HashMap<>(); @@ -29,14 +93,14 @@ public static FieldSelector parse(String spec) { int i = 0; while (i < spec.length()) { int commaIdx = nextCommaOrEnd(spec, i); - int parenIdx = spec.indexOf('(', i); + int parenthesisIdx = spec.indexOf('(', i); - if (parenIdx != -1 && parenIdx < commaIdx) { - String name = spec.substring(i, parenIdx); - int closeParen = findMatchingParen(spec, parenIdx); - String inner = spec.substring(parenIdx + 1, closeParen); + if (parenthesisIdx != -1 && parenthesisIdx < commaIdx) { + String name = spec.substring(i, parenthesisIdx); + int closeParenthesisIdx = findMatchingParen(spec, parenthesisIdx); + String inner = spec.substring(parenthesisIdx + 1, closeParenthesisIdx); nested.put(name, parse(inner)); - i = closeParen + 1; + i = closeParenthesisIdx + 1; if (i < spec.length() && spec.charAt(i) == ',') i++; } else { fields.add(spec.substring(i, commaIdx)); @@ -46,16 +110,49 @@ public static FieldSelector parse(String spec) { return new FieldSelector(fields, nested); } + /** + * Checks whether the specified field should be included in serialization. + *

+ * A field is included if: + *

    + *
  • The selector is in unrestricted mode (fields is null), or
  • + *
  • The field is explicitly listed in the fields set, or
  • + *
  • The field has a nested selector defined
  • + *
+ *

+ * + * @param field the name of the field to check + * @return true if the field should be included, false otherwise + */ public boolean includes(String field) { if (fields == null) return true; if (fields.contains(field)) return true; return nested != null && nested.containsKey(field); } + /** + * Checks whether this selector is in unrestricted mode, allowing all fields. + *

+ * Unrestricted mode occurs when no field specification was provided during parsing + * (i.e., the specification was null or blank). + *

+ * + * @return true if all fields should be included without restrictions, false otherwise + */ public boolean includeAll() { return fields == null; } + /** + * Retrieves the nested FieldSelector for the specified field. + *

+ * If no nested selector is defined for the field, returns an unrestricted selector + * that allows all fields at the nested level. + *

+ * + * @param field the name of the field whose nested selector to retrieve + * @return the nested FieldSelector for the field, or an unrestricted selector if none exists + */ public FieldSelector nested(String field) { if (nested == null || !nested.containsKey(field)) { return new FieldSelector(null, null); // no restriction on nested level either @@ -63,6 +160,18 @@ public FieldSelector nested(String field) { return nested.get(field); } + /** + * Finds the index of the next comma at the current nesting depth, or the end of the string. + *

+ * This method tracks parenthesis depth to avoid splitting on commas that are inside + * nested field specifications. Only commas at depth 0 (not inside any parentheses) + * are considered as field separators. + *

+ * + * @param spec the specification string to search + * @param from the starting index for the search + * @return the index of the next comma at depth 0, or the length of the string if none found + */ private static int nextCommaOrEnd(String spec, int from) { int depth = 0; for (int i = from; i < spec.length(); i++) { @@ -74,6 +183,18 @@ private static int nextCommaOrEnd(String spec, int from) { return spec.length(); } + /** + * Finds the index of the closing parenthesis that matches the opening parenthesis at the specified position. + *

+ * This method correctly handles nested parentheses by tracking the depth level and returning + * the index of the closing parenthesis that brings the depth back to 0. + *

+ * + * @param spec the specification string to search + * @param openIdx the index of the opening parenthesis + * @return the index of the matching closing parenthesis + * @throws IllegalArgumentException if no matching closing parenthesis is found + */ private static int findMatchingParen(String spec, int openIdx) { int depth = 0; for (int i = openIdx; i < spec.length(); i++) { diff --git a/application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelectorHolder.java b/application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelectorHolder.java index be8015ff91a..fd21df71241 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelectorHolder.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelectorHolder.java @@ -5,10 +5,32 @@ import org.springframework.stereotype.Component; import org.springframework.web.context.annotation.RequestScope; +/** + * Request-scoped holder component for managing {@link FieldSelector} instances during HTTP request processing. + *

+ * This component is used in conjunction with {@link FieldSelectorInterceptor} to parse and store field selection + * criteria from HTTP request parameters (typically the "fields" query parameter). The held {@link FieldSelector} + * is then used by custom serializers like {@link LocalizedEventOutcomeSerializer} to control which fields + * are included in JSON response bodies. + *

+ *

+ * Being request-scoped ensures that each HTTP request has its own isolated instance of this holder, + * preventing thread-safety issues in concurrent request processing. + *

+ * + * @see FieldSelector + * @see FieldSelectorInterceptor + * @see LocalizedEventOutcomeSerializer + */ @Setter @Getter @Component @RequestScope public class FieldSelectorHolder { + + /** + * The field selector that determines which fields should be included during serialization. + * Initialized with a default selector that includes all fields (parsed from null). + */ private FieldSelector selector = FieldSelector.parse(null); } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelectorInterceptor.java b/application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelectorInterceptor.java index afb1aced8a2..e3e53292f2d 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelectorInterceptor.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelectorInterceptor.java @@ -16,8 +16,7 @@ public FieldSelectorInterceptor(FieldSelectorHolder holder) { } @Override - public boolean preHandle(HttpServletRequest request, @NonNull HttpServletResponse response, - @NonNull Object handler) { + public boolean preHandle(HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) { String fields = request.getParameter("fields"); holder.setSelector(FieldSelector.parse(fields)); return true; diff --git a/application-engine/src/main/java/com/netgrif/application/engine/serialization/LocalizedEventOutcomeSerializer.java b/application-engine/src/main/java/com/netgrif/application/engine/serialization/LocalizedEventOutcomeSerializer.java index ab8bb93cb3d..26e511f04b1 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/serialization/LocalizedEventOutcomeSerializer.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/serialization/LocalizedEventOutcomeSerializer.java @@ -13,16 +13,64 @@ import java.io.IOException; +/** + * Custom JSON serializer for {@link LocalisedEventOutcome} objects that provides selective field serialization + * based on a {@link FieldSelector} configuration. + *

+ * This serializer handles various types of localized event outcomes including: + *

    + *
  • {@link LocalisedPetriNetEventOutcome} - outcomes related to Petri nets
  • + *
  • {@link LocalisedCaseEventOutcome} - outcomes related to cases
  • + *
  • {@link LocalisedTaskEventOutcome} - outcomes related to tasks
  • + *
  • {@link LocalisedGetDataEventOutcome} - outcomes for data retrieval operations
  • + *
  • {@link LocalisedGetDataGroupsEventOutcome} - outcomes for data groups retrieval
  • + *
  • {@link LocalisedSetDataEventOutcome} - outcomes for data modification operations
  • + *
+ * The serializer uses a {@link DynamicFieldSerializer} to handle nested object serialization with field selection support. + *

+ * + * @see LocalisedEventOutcome + * @see FieldSelector + * @see DynamicFieldSerializer + */ public class LocalizedEventOutcomeSerializer extends JsonSerializer { + /** + * Holder containing the {@link FieldSelector} that determines which fields should be included + * in the serialized JSON output. + */ private final FieldSelectorHolder selectorHolder; + + /** + * Serializer used for dynamic field serialization of nested objects with field selection support. + */ private final DynamicFieldSerializer dynamicSerializer; + /** + * Constructs a new LocalizedEventOutcomeSerializer with the specified field selector holder. + * + * @param selectorHolder the holder containing the field selector configuration for controlling + * which fields are included in the serialized output + */ public LocalizedEventOutcomeSerializer(FieldSelectorHolder selectorHolder) { this.selectorHolder = selectorHolder; this.dynamicSerializer = new DynamicFieldSerializer(selectorHolder); } + /** + * Serializes a {@link LocalisedEventOutcome} object to JSON format with selective field inclusion + * based on the configured {@link FieldSelector}. + *

+ * The method handles serialization of common fields (message, frontActions, outcomes) and type-specific + * fields based on the actual runtime type of the outcome object. Nested objects are serialized using + * the {@link DynamicFieldSerializer} with their corresponding nested field selectors. + *

+ * + * @param outcome the localized event outcome object to serialize + * @param gen the JSON generator used to write JSON content + * @param provider the serializer provider for accessing additional serializers + * @throws IOException if an I/O error occurs during serialization + */ @Override public void serialize(LocalisedEventOutcome outcome, JsonGenerator gen, SerializerProvider provider) throws IOException { From 88a71a88756561a1fcd7ebeca97c1bae4b3e86e9 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Tue, 16 Jun 2026 12:12:26 +0200 Subject: [PATCH 5/6] Improve robustness of `FieldSelector` parsing Enhanced handling of blank or null specifications, added trimming for field names, and ensured empty names are excluded during parsing. --- .../engine/serialization/FieldSelector.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelector.java b/application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelector.java index 0d6cd7ffd1f..d084f5fad99 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelector.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/serialization/FieldSelector.java @@ -83,27 +83,29 @@ private FieldSelector(Set fields, Map nested) { * @throws IllegalArgumentException if the specification contains unmatched parentheses */ public static FieldSelector parse(String spec) { - Set fields = new HashSet<>(); - Map nested = new HashMap<>(); - if (spec == null || spec.isBlank()) { - return new FieldSelector(); + return new FieldSelector(null, null); } + Set fields = new HashSet<>(); + Map nested = new HashMap<>(); + int i = 0; while (i < spec.length()) { int commaIdx = nextCommaOrEnd(spec, i); int parenthesisIdx = spec.indexOf('(', i); if (parenthesisIdx != -1 && parenthesisIdx < commaIdx) { - String name = spec.substring(i, parenthesisIdx); + String name = spec.substring(i, parenthesisIdx).trim(); int closeParenthesisIdx = findMatchingParen(spec, parenthesisIdx); String inner = spec.substring(parenthesisIdx + 1, closeParenthesisIdx); + fields.add(name); nested.put(name, parse(inner)); i = closeParenthesisIdx + 1; if (i < spec.length() && spec.charAt(i) == ',') i++; } else { - fields.add(spec.substring(i, commaIdx)); + String name = spec.substring(i, commaIdx).trim(); + if (!name.isEmpty()) fields.add(name); i = commaIdx + 1; } } From b68cae7b017da8297ed7e4d409dbe3ec79956686 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Mon, 6 Jul 2026 12:38:54 +0200 Subject: [PATCH 6/6] [NAE-2451] Optimize and Reduce Outcome Size - Refactor serialization components to replace `JsonSerializer` with `ValueSerializer` and update `ObjectMapper` configuration for improved compatibility. --- .../serialization/DynamicFieldSerializer.java | 32 ++++++------- .../LocalizedEventOutcomeSerializer.java | 45 ++++++++++--------- .../ObjectMapperConfiguration.java | 12 +++-- 3 files changed, 47 insertions(+), 42 deletions(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/serialization/DynamicFieldSerializer.java b/application-engine/src/main/java/com/netgrif/application/engine/serialization/DynamicFieldSerializer.java index e48fa9de5d3..caf502df6e9 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/serialization/DynamicFieldSerializer.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/serialization/DynamicFieldSerializer.java @@ -1,11 +1,13 @@ package com.netgrif.application.engine.serialization; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.BeanDescription; -import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition; +import tools.jackson.core.JsonGenerator; +import tools.jackson.databind.BeanDescription; +import tools.jackson.databind.JavaType; +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.ValueSerializer; +import tools.jackson.databind.introspect.BeanPropertyDefinition; import java.io.IOException; @@ -41,7 +43,7 @@ * @see FieldSelector * @see FieldSelectorHolder */ -public class DynamicFieldSerializer extends JsonSerializer { +public class DynamicFieldSerializer extends ValueSerializer { /** * Holder containing the field selector used to determine which fields should be serialized. @@ -68,9 +70,8 @@ public DynamicFieldSerializer(FieldSelectorHolder holder) { * @throws IOException if an I/O error occurs during serialization */ @Override - public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) - throws IOException { - serializeWithSelector(value, gen, provider, holder.getSelector()); + public void serialize(Object value, JsonGenerator gen, SerializationContext context) { + serializeWithSelector(value, gen, context, holder.getSelector()); } /** @@ -88,8 +89,7 @@ public void serialize(Object value, JsonGenerator gen, SerializerProvider provid * @param selector the field selector that determines which fields to include in serialization * @throws IOException if an I/O error occurs during serialization */ - public void serializeWithSelector(Object value, JsonGenerator gen, - SerializerProvider provider, FieldSelector selector) throws IOException { + public void serializeWithSelector(Object value, JsonGenerator gen, SerializationContext context, FieldSelector selector) { if (value == null) { gen.writeNull(); @@ -97,12 +97,12 @@ public void serializeWithSelector(Object value, JsonGenerator gen, } if (selector.includeAll()) { - provider.defaultSerializeValue(value, gen); + context.findValueSerializer(value.getClass()).serialize(value, gen, context); return; } - JavaType type = provider.constructType(value.getClass()); - BeanDescription desc = provider.getConfig().introspect(type); + JavaType type = context.constructType(value.getClass()); + BeanDescription desc = context.introspectBeanDescription(type); gen.writeStartObject(); for (BeanPropertyDefinition prop : desc.findProperties()) { @@ -113,13 +113,13 @@ public void serializeWithSelector(Object value, JsonGenerator gen, } Object propValue = prop.getAccessor().getValue(value); - gen.writeFieldName(name); + gen.writeName(name); FieldSelector nested = selector.nested(name); if (nested.includeAll()) { - provider.defaultSerializeValue(propValue, gen); + context.findValueSerializer(propValue.getClass()).serialize(propValue, gen, context); } else { - serializeWithSelector(propValue, gen, provider, nested); + serializeWithSelector(propValue, gen, context, nested); } } gen.writeEndObject(); diff --git a/application-engine/src/main/java/com/netgrif/application/engine/serialization/LocalizedEventOutcomeSerializer.java b/application-engine/src/main/java/com/netgrif/application/engine/serialization/LocalizedEventOutcomeSerializer.java index 26e511f04b1..4f544a24c63 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/serialization/LocalizedEventOutcomeSerializer.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/serialization/LocalizedEventOutcomeSerializer.java @@ -1,8 +1,5 @@ package com.netgrif.application.engine.serialization; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; import com.netgrif.application.engine.workflow.web.responsebodies.eventoutcomes.LocalisedGetDataEventOutcome; import com.netgrif.application.engine.workflow.web.responsebodies.eventoutcomes.LocalisedGetDataGroupsEventOutcome; import com.netgrif.application.engine.workflow.web.responsebodies.eventoutcomes.LocalisedSetDataEventOutcome; @@ -10,6 +7,10 @@ import com.netgrif.application.engine.workflow.web.responsebodies.eventoutcomes.base.LocalisedEventOutcome; import com.netgrif.application.engine.workflow.web.responsebodies.eventoutcomes.base.LocalisedPetriNetEventOutcome; import com.netgrif.application.engine.workflow.web.responsebodies.eventoutcomes.base.LocalisedTaskEventOutcome; +import org.springframework.boot.jackson.JacksonComponent; +import tools.jackson.core.JsonGenerator; +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.ValueSerializer; import java.io.IOException; @@ -33,7 +34,8 @@ * @see FieldSelector * @see DynamicFieldSerializer */ -public class LocalizedEventOutcomeSerializer extends JsonSerializer { +@JacksonComponent(type = LocalisedEventOutcome.class) +public class LocalizedEventOutcomeSerializer extends ValueSerializer { /** * Holder containing the {@link FieldSelector} that determines which fields should be included @@ -72,48 +74,47 @@ public LocalizedEventOutcomeSerializer(FieldSelectorHolder selectorHolder) { * @throws IOException if an I/O error occurs during serialization */ @Override - public void serialize(LocalisedEventOutcome outcome, JsonGenerator gen, SerializerProvider provider) - throws IOException { + public void serialize(LocalisedEventOutcome outcome, JsonGenerator gen, SerializationContext context) { gen.writeStartObject(); FieldSelector selector = selectorHolder.getSelector(); if (selector.includes("message") && outcome.getMessage() != null) { - gen.writeObjectField("message", outcome.getMessage()); + gen.writePOJOProperty("message", outcome.getMessage()); } if (selector.includes("frontActions") && outcome.getFrontActions() != null) { - gen.writeObjectField("frontActions", outcome.getFrontActions()); + gen.writePOJOProperty("frontActions", outcome.getFrontActions()); } if (selector.includes("outcomes") && outcome.getOutcomes() != null) { - gen.writeArrayFieldStart("outcomes"); + gen.writeArrayPropertyStart("outcomes"); for (LocalisedEventOutcome child : outcome.getOutcomes()) { - provider.defaultSerializeValue(child, gen); + context.findValueSerializer(child.getClass()).serialize(child, gen, context); } gen.writeEndArray(); } if (outcome instanceof LocalisedPetriNetEventOutcome specificOutcome && selector.includes("net") && specificOutcome.getNet() != null) { - gen.writeFieldName("net"); - dynamicSerializer.serializeWithSelector(specificOutcome.getNet(), gen, provider, selector.nested("net")); + gen.writeName("net"); + dynamicSerializer.serializeWithSelector(specificOutcome.getNet(), gen, context, selector.nested("net")); } if (outcome instanceof LocalisedCaseEventOutcome specificOutcome && selector.includes("case") && specificOutcome.getaCase() != null) { - gen.writeFieldName("aCase"); - dynamicSerializer.serializeWithSelector(specificOutcome.getaCase(), gen, provider, selector.nested("case")); + gen.writeName("aCase"); + dynamicSerializer.serializeWithSelector(specificOutcome.getaCase(), gen, context, selector.nested("case")); } if (outcome instanceof LocalisedTaskEventOutcome specificOutcome && selector.includes("task") && specificOutcome.getTask() != null) { - gen.writeFieldName("task"); - dynamicSerializer.serializeWithSelector(specificOutcome.getTask(), gen, provider, selector.nested("task")); + gen.writeName("task"); + dynamicSerializer.serializeWithSelector(specificOutcome.getTask(), gen, context, selector.nested("task")); } if (outcome instanceof LocalisedGetDataEventOutcome specificOutcome && selector.includes("data") && specificOutcome.getData() != null) { - gen.writeFieldName("data"); - dynamicSerializer.serializeWithSelector(specificOutcome.getData(), gen, provider, selector.nested("data")); + gen.writeName("data"); + dynamicSerializer.serializeWithSelector(specificOutcome.getData(), gen, context, selector.nested("data")); } if (outcome instanceof LocalisedGetDataGroupsEventOutcome specificOutcome && selector.includes("data") && specificOutcome.getData() != null) { - gen.writeFieldName("data"); - dynamicSerializer.serializeWithSelector(specificOutcome.getData(), gen, provider, selector.nested("data")); + gen.writeName("data"); + dynamicSerializer.serializeWithSelector(specificOutcome.getData(), gen, context, selector.nested("data")); } if (outcome instanceof LocalisedSetDataEventOutcome specificOutcome && selector.includes("changedFields") && specificOutcome.getChangedFields() != null) { - gen.writeFieldName("changedFields"); - dynamicSerializer.serializeWithSelector(specificOutcome.getChangedFields(), gen, provider, selector.nested("changedFields")); + gen.writeName("changedFields"); + dynamicSerializer.serializeWithSelector(specificOutcome.getChangedFields(), gen, context, selector.nested("changedFields")); } gen.writeEndObject(); diff --git a/application-engine/src/main/java/com/netgrif/application/engine/serialization/ObjectMapperConfiguration.java b/application-engine/src/main/java/com/netgrif/application/engine/serialization/ObjectMapperConfiguration.java index d6f16c23c20..f311a8205c1 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/serialization/ObjectMapperConfiguration.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/serialization/ObjectMapperConfiguration.java @@ -2,16 +2,20 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.netgrif.application.engine.workflow.web.responsebodies.eventoutcomes.base.LocalisedEventOutcome; -import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import tools.jackson.databind.module.SimpleModule; @Configuration public class ObjectMapperConfiguration { @Bean - public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer(FieldSelectorHolder holder) { - return builder -> builder.serializationInclusion(JsonInclude.Include.NON_NULL) - .serializerByType(LocalisedEventOutcome.class, new LocalizedEventOutcomeSerializer(holder)); + public JsonMapperBuilderCustomizer jsonCustomizer(FieldSelectorHolder holder) { + SimpleModule module = new SimpleModule(); + module.addSerializer(LocalisedEventOutcome.class, new LocalizedEventOutcomeSerializer(holder)); + return builder -> builder + .changeDefaultPropertyInclusion(include -> include.withValueInclusion(JsonInclude.Include.NON_NULL)) + .findAndAddModules(); } }