Spring Cloud Stream: Spring Boot 3.x: JsonProperty, JsonIgnoreProperties behaving unexpectedly with Jackson in Message Converter

huangapple go评论62阅读模式
英文:

Spring Cloud Stream: Spring Boot 3.x: JsonProperty, JsonIgnoreProperties behaving unexpectedly with Jackson in Message Converter

问题

I'm here to provide translations of the text you provided. Please let me know which parts you'd like me to translate.

英文:

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

答案1

得分: 2

这种行为的原因在于你的 DummyMessage 配置。你有一个 String numbers 字段,但在 Jackson 中忽略了它。相反,你提供了 getToken 方法。因此,Jackson 认为你的对象只有 List<String> tokens 字段,并且在解组时无法理解你的字节。

仔细看一下:

当你调用 /mvc 端点时,只有编组发生,所以它正常工作。你有以下流程:

请求体 (1) -> DummyMessage1 (2) -> 响应体

{
  "numbers": "6469344427; 2017586291"
}

(1) ===> 

DummyMessage1(
  String numbers = "6469344427; 2017586291"
)

(2) ===> 

{
  "tokens": [
    "6469344427",
    "2017586291"
  ]
}

但在你的 /message 端点,流程更长:

请求体 (1) -> DummyMessage2 (2) -> Json (3) -> DummyMessage2

在第 3 步,即在你的 DummyMessageConsumerService 中,Spring 尝试从 JSON 中解组 DummyMessage2,但无法做到这一点,因为 Json 中只有字段 "tokens",但在对象中没有这样的字段。

为了解决这个问题:

  1. getTokens() 方法中移除 @JsonProperty(access = JsonProperty.Access.READ_ONLY)

  2. 在你的 DummyMessage2 中添加以下 setter:

public void setTokens(List<String> tokens) {
    this.numbers = String.join(";", tokens);
}

或者你可以创建另一个 DTO 对象,像 DummyMessage3,其中包含 List&lt;String&gt; tokens 而不是 String numbers。在这种情况下,流程将是 (1) -> DummyMessage2 (2) -> Json (3) -> DummyMessage3

英文:

The reason for such behavior is in your DummyMessage configuration. You have the String numbers field but ignore it for Jackson. Instead, you provide the getToken method. So Jackson thinks that your object has only the List&lt;String&gt; tokens field and doesn't understand your bytes when you unmarshal it.

Let's look closer:

When you call your /mvc endpoint, there is only marshaling happening, so it works. You have the following flow:

Request body (1)-&gt; DummyMessage1 (2)-&gt; Response body

{
  &quot;numbers&quot;: &quot;6469344427; 2017586291&quot;
}

(1) ====&gt; 

DummyMessage1(
  String numbers = &quot;6469344427; 2017586291&quot;
)

(2) ====&gt; 

{
  &quot;tokens&quot;: [
    &quot;6469344427&quot;,
    &quot;2017586291&quot;
  ]
}

But in your /message endpoint, the flow is longer:

Request body (1)-&gt; DummyMessage2 (2)-&gt; Json (3)-&gt; DummyMessage2

And on step 3, at your DummyMessageConsumerService, spring tries to unmarshal DummyMessage2 from JSON and can't do this because the Json has only the field "tokens," but there is no such field in the object.

To fix this issue:

  1. Remove @JsonProperty(access = JsonProperty.Access.READ_ONLY) from getTokens() method

  2. Add the following setter to your DummyMessage2

public void setTokens(List&lt;String&gt; tokens) {
    this.numbers = String.join(&quot;;&quot;, tokens);
}

Or you can create another DTO object like DummyMessage3 with List&lt;String&gt; tokens instead of String numbers. In that case the flow will be (1) -&gt; DummyMessage2 (2)-&gt; Json (3)-&gt; DummyMessage3.

huangapple
  • 本文由 发表于 2023年5月13日 19:51:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/76242570.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定