Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] Support @JsonInclude(Include.NON_EMPTY) #2641

Closed
jorgerod opened this issue May 29, 2024 · 12 comments
Closed

[BUG] Support @JsonInclude(Include.NON_EMPTY) #2641

jorgerod opened this issue May 29, 2024 · 12 comments
Labels
bug Something isn't working fixed
Milestone

Comments

@jorgerod
Copy link

Hi

We are integrating FastJson2 as the serializer of our SpringBoot based project.
The performance is much better than Jackson but I have encountered a problem that is blocking us and preventing me from continuing.

SpringBoot uses the SystemHealth object for the actuator endpoint and we need the response from FastJson2 and Jackson to be identical.

This object has fields annotated with @JsonInclude(Include.NON_EMPTY) and FastJson2 is ignoring this property (it is not possible to change the annotation to one of FastJson2's own)

Test:

package org.springframework.boot.actuate.health;

import java.util.Collections;
import java.util.HashMap;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.endpoint.ApiVersion;

import static org.assertj.core.api.Assertions.assertThat;

class FastJson2Test {

  @Test
  void test() throws JsonProcessingException {
    //given
    final SystemHealth systemHealth =
        new SystemHealth(ApiVersion.V3, Status.UP, new HashMap<>(), Collections.emptySet());

    //when
    String jackson = new ObjectMapper().writeValueAsString(systemHealth);   
    String fastjson2 = JSON.toJSONString(systemHealth, JSONWriter.Feature.WriteNonStringValueAsString);
    
    //then
    assertThat(jackson).doesNotContain("components\":{}");
    assertThat(fastjson2).doesNotContain("components\":{}"); // don't work
  }

Result jackson

{"status":"UP"}

Result fastjson2

{"components":{},"groups":[],"status":{"description":"","status":"UP"}}

Related issue:

@jorgerod jorgerod added the bug Something isn't working label May 29, 2024
@wenshao wenshao added this to the 2.0.51 milestone May 29, 2024
@wenshao
Copy link
Member

wenshao commented May 29, 2024

https://oss.sonatype.org/content/repositories/snapshots/com/alibaba/fastjson2/fastjson2/2.0.51-SNAPSHOT/

The BUG has been fixed. Please help verify it with version 2.0.51-SNAPSHOT, 2.0.51 will be released on June 2

@wenshao wenshao added the fixed label May 29, 2024
@jorgerod
Copy link
Author

Hi @wenshao

Thank you very much, especially for the speed.

In that same example, I have found another annotation that is being ignored. @JsonUnwrapped

import static org.assertj.core.api.Assertions.assertThat;

import com.alibaba.fastjson2.JSON;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;

class FastJson2JsonUnwrappedTest {

  @Test
  void testJsonUnwrapped() throws JsonProcessingException {
    final TestJsonUnwrappedDTO testJsonUnwrapped = new TestJsonUnwrappedDTO();

    // when
    final String jackson = new ObjectMapper().writeValueAsString(testJsonUnwrapped);
    final String fastjson2 = JSON.toJSONString(testJsonUnwrapped);

    // then
    assertThat(jackson).isEqualTo("{\"status\":\"UP\"}");
    assertThat(fastjson2).contains("{\"status\":\"UP\"}"); //Don't work
  }

  private class TestJsonUnwrappedDTO {
    private final Status status = Status.UP;

    @JsonUnwrapped
    public Status getStatus() {
      return this.status;
    }
  }
}

@wenshao
Copy link
Member

wenshao commented May 29, 2024

Please provide the Java source code of Status for constructing testcase

@jorgerod
Copy link
Author

jorgerod commented May 29, 2024

Hi

Sorry, I update the rest with the Status class.

import static org.assertj.core.api.Assertions.assertThat;

import com.alibaba.fastjson2.JSON;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;

class FastJson2JsonUnwrappedTest {

  @Test
  void testJsonUnwrapped() throws JsonProcessingException {
    final TestJsonUnwrappedDTO testJsonUnwrapped = new TestJsonUnwrappedDTO();

    // when
    final String jackson = new ObjectMapper().writeValueAsString(testJsonUnwrapped);
    final String fastjson2 = JSON.toJSONString(testJsonUnwrapped);

    // then
    assertThat(jackson).isEqualTo("{\"status\":\"UP\"}");
    assertThat(fastjson2).contains("{\"status\":\"UP\"}"); //doesn't work
  }

  private class TestJsonUnwrappedDTO {
    private final Status status = Status.UP;

    @JsonUnwrapped
    public Status getStatus() {
      return this.status;
    }
  }

  @JsonInclude(Include.NON_EMPTY)
  static class Status {

    public static final Status UP = new Status("UP");

    private final String code;

    private final String description;

    public Status(final String code) {
      this(code, "");
    }

    public Status(final String code, final String description) {
      this.code = code;
      this.description = description;
    }

    @JsonProperty("status")
    public String getCode() {
      return this.code;
    }

    @JsonInclude(Include.NON_EMPTY)
    public String getDescription() {
      return this.description;
    }
  }
}

Result jackson

{"status":"UP"}

Result fastjson2

{"status":{"description":"","status":"UP"}}

Thank you very much

@jorgerod
Copy link
Author

jorgerod commented May 29, 2024

Hi

And testing the original case in detail with 2.0.51-SNAPSHOT, I have discovered a strange behavior.

I add an example to show it better:

import static org.assertj.core.api.Assertions.assertThat;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.alibaba.fastjson2.JSON;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;

class FastJson2Test {

  @Test
  void test2() throws JsonProcessingException {
    // given
    final TestJsonIncludeDTO dto = new TestJsonIncludeDTO();

    // when
    final String jackson = new ObjectMapper().writeValueAsString(dto);
    final String fastjson2 = JSON.toJSONString(dto);

    // then
    assertThat(jackson).isEqualTo("{}");
    assertThat(fastjson2).isEqualTo("{}"); // fails
  }

  static class TestJsonIncludeDTO {

    private final Map<String, String> map = new HashMap<>();

    private final List<String> list = new ArrayList<>();

    private final Set<String> set = new HashSet<>();

    private final String strEmpty = "";

    private final String strNull = null;

    @JsonInclude(Include.NON_EMPTY)
    @JsonProperty
    public Map<String, String> getMap() {
      return this.map;
    }

    @JsonInclude(Include.NON_EMPTY)
    public List<String> getList() {
      return this.list;
    }

    @JsonInclude(Include.NON_EMPTY)
    public Set<String> getSet() {
      return this.set;
    }

    @JsonInclude(Include.NON_EMPTY)
    public String getStrEmpty() {
      return this.strEmpty;
    }

    @JsonInclude(Include.NON_NULL)
    public String getStrNull() {
      return this.strNull;
    }
  }

}

Result jackson

{}

Result fastjson2

{"map":{},"set":[],"strEmpty":""}

Thank you very much

EDIT: I have updated the example by adding a string

@wenshao
Copy link
Member

wenshao commented May 29, 2024

https://oss.sonatype.org/content/repositories/snapshots/com/alibaba/fastjson2/fastjson2/2.0.51-SNAPSHOT/
The problem has been fixed. Please update the SNAPSHOT and help re-verify.

@jorgerod
Copy link
Author

jorgerod commented May 30, 2024

Hello @wenshao

Great, thank you very much for your speed.

I have already re-verified and the @JsonUnwrapped case has been fixed but the second case is still failing.

Hi

And testing the original case in detail with 2.0.51-SNAPSHOT, I have discovered a strange behavior.

I add an example to show it better:

import static org.assertj.core.api.Assertions.assertThat;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.alibaba.fastjson2.JSON;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;

class FastJson2Test {

  @Test
  void test2() throws JsonProcessingException {
    // given
    final TestJsonIncludeDTO dto = new TestJsonIncludeDTO();

    // when
    final String jackson = new ObjectMapper().writeValueAsString(dto);
    final String fastjson2 = JSON.toJSONString(dto);

    // then
    assertThat(jackson).isEqualTo("{}");
    assertThat(fastjson2).isEqualTo("{}"); // fails
  }

  static class TestJsonIncludeDTO {

    private final Map<String, String> map = new HashMap<>();

    private final List<String> list = new ArrayList<>();

    private final Set<String> set = new HashSet<>();

    private final String strEmpty = "";

    private final String strNull = null;

    @JsonInclude(Include.NON_EMPTY)
    @JsonProperty
    public Map<String, String> getMap() {
      return this.map;
    }

    @JsonInclude(Include.NON_EMPTY)
    public List<String> getList() {
      return this.list;
    }

    @JsonInclude(Include.NON_EMPTY)
    public Set<String> getSet() {
      return this.set;
    }

    @JsonInclude(Include.NON_EMPTY)
    public String getStrEmpty() {
      return this.strEmpty;
    }

    @JsonInclude(Include.NON_NULL)
    public String getStrNull() {
      return this.strNull;
    }
  }

}

Result jackson

{}

Result fastjson2

{"map":{},"set":[],"strEmpty":""}

Thank you very much

EDIT: I have updated the example by adding a string

Result before last commit

{"map":{},"set":[],"strEmpty":""}

Result after last commit

{"set":[],"map":{}}

Result jackson

{}

Thank you very much

@wenshao
Copy link
Member

wenshao commented May 30, 2024

https://oss.sonatype.org/content/repositories/snapshots/com/alibaba/fastjson2/fastjson2/2.0.51-SNAPSHOT/
The problem "Include.NON_EMPTY not support Map & Collection" has been fixed. Please update the SNAPSHOT and help re-verify.

@jorgerod
Copy link
Author

Thank you very much

I am seeing strange behavior. If the DTO is inner class it works fine but if it is a normal class it doesn't go well.

Test

class FastJson2Test {

  @Test
  void testJsonInclude() throws JsonProcessingException {
    // given
    final JsonIncludeDTO dto = new JsonIncludeDTO();

    // when
    final String jackson = new ObjectMapper().writeValueAsString(dto);
    final String fastjson2 = JSON.toJSONString(dto);

    // then
    assertThat(jackson).isEqualTo("{}");
    assertThat(fastjson2).isEqualTo("{}"); // fails
  }
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;

public class JsonIncludeDTO {

  private final Map<String, String> map = new HashMap<>();

  private final List<String> list = new ArrayList<>();

  private final Set<String> set = new HashSet<>();

  private final String strEmpty = "";

  private final String strNull = null;

  @JsonInclude(Include.NON_EMPTY)
  @JsonProperty
  public Map<String, String> getMap() {
    return this.map;
  }

  @JsonInclude(Include.NON_EMPTY)
  public List<String> getList() {
    return this.list;
  }

  @JsonInclude(Include.NON_EMPTY)
  public Set<String> getSet() {
    return this.set;
  }

  @JsonInclude(Include.NON_EMPTY)
  public String getStrEmpty() {
    return this.strEmpty;
  }

  @JsonInclude(Include.NON_NULL)
  public String getStrNull() {
    return this.strNull;
  }
}

Result

{"list":[],"map":{}}

@wenshao
Copy link
Member

wenshao commented May 30, 2024

https://oss.sonatype.org/content/repositories/snapshots/com/alibaba/fastjson2/fastjson2/2.0.51-SNAPSHOT/
The problem has been fixed. Please update the SNAPSHOT and help re-verify again.

@jorgerod
Copy link
Author

Perfect!
Thank you very much for everything. We are looking forward to the release.

@wenshao
Copy link
Member

wenshao commented Jun 1, 2024

https://github.com/alibaba/fastjson2/releases/tag/2.0.51
Please use the new version 2.0.51

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working fixed
Projects
None yet
Development

No branches or pull requests

2 participants