I am currently working on a Spring Boot application (version 3.0.6) and using Spring Cloud (version 2022.0.2). I have two different endpoints ("/mvc" and "/message") that use two different request objects (DummyMessage1 and DummyMessage2), and I'm having some issues with Jackson's JsonProperty and JsonIgnoreProperties annotations.
In both the cases I'm looking to supress numbers from response and get tokens in response.
I have used JsonIgnoreProperties to suppress numbers from response.
@JsonIgnoreProperties(ignoreUnknown = true, value = {"numbers"}, allowSetters = true)
In the /mvc
endpoint, everything is working as expected. I'm using DummyMessage1
request object for this endpoint. The numbers
field is suppressed from response as expected. Furthermore, the tokens
field, is returned successfully in the JSON response and is not empty as it contains the list of tokens from the request.
The problem arises in the /message
endpoint, which uses DummyMessage2
to post a message to kafka. DummyMessage2
is identical to DummyMessage1
.
When I attempted to post a JSON to the Kafka topic, an exception was thrown:
Full stacktrace can be found here
java.lang.ClassCastException: class [B cannot be cast to class com.example.marshaller.model.DummyMessage2 ([B is in module java.base of loader 'bootstrap'; com.example.marshaller.model.DummyMessage2 is in unnamed module of loader 'app')
at org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry$FunctionInvocationWrapper.invokeConsumer(SimpleFunctionRegistry.java:990) ~[spring-cloud-function-context-4.0.2.jar:4.0.2]
Here are the request objects:
DummyMessage1
:
@EqualsAndHashCode(callSuper = false)
@Getter
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true, value = {"numbers"}, allowSetters = true)
public class DummyMessage1 extends BaseRequest {
private String numbers;
//@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public List<String> getTokens() {
if (StringUtils.isBlank(this.numbers)) return Collections.emptyList();
return List.of(this.numbers.split(";\\s*"));
}
}
DummyMessage2
:
@EqualsAndHashCode(callSuper = false)
@Getter
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true, value = {"numbers"}, allowSetters = true)
public class DummyMessage2 extends BaseRequest {
private String numbers;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public List<String> getTokens() {
if (StringUtils.isBlank(this.numbers)) return Collections.emptyList();
return List.of(this.numbers.split(";\\s*"));
}
}
Next, to work around the the exception, I added the annotation @JsonProperty(access = JsonProperty.Access.READ_ONLY) on getTokens() method in DummyMessage2. However, this did not result in the desired outcome. The JSON posted to Kafka now did not include the numbers field as required, but also, the tokens field was as empty, which is not expected.
Why is this happening? I would expect the tokens field to be populated as it is in the /mvc endpoint. Any help or insights would be greatly appreciated.
Here's repository to reproduce the issue: https://github.com/cricketbackground/marshaller
Please note for security reasons the kafka brokers and kafka zk nodes are purposefully not set in the repo.
Request Body:
{
"numbers":"12345; 3982934823; 3248923492834; 324923434"
}
How to use: please see here
--- Update
Here's the simple working DummyMessage2 by adding a field for the utility getter. In the getter method the field itself is returned if it is not empty, thus retaining the field value across multiple message post hops
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;
import java.util.Collections;
import java.util.List;
@EqualsAndHashCode(callSuper = false)
@Getter
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true, value = {"numbers"}, allowSetters = true)
public class DummyMessage2 extends BaseRequest {
private String numbers;
private List<String> tokens;
public List<String> getTokens() {
if (!CollectionUtils.isEmpty(this.tokens)) return this.tokens;
if (StringUtils.isBlank(this.numbers)) return Collections.emptyList();
return List.of(this.numbers.split(";\\s*"));
}
}
The other solution suggested by Yevhenii Semenov also works but requires setter method logic to be written exactly reverse of getter logic. Setter method complexity increases based on getter method. Also, if there are more fields then writing/maintaining setter methods for each field becomes a problem