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

@JsonValue ignores annotations on annotated field (particularly @JsonInclude) #4762

Open
1 task done
sikorskii opened this issue Oct 23, 2024 · 4 comments
Open
1 task done
Labels

Comments

@sikorskii
Copy link

sikorskii commented Oct 23, 2024

Search before asking

  • I searched in the issues and found nothing similar.

Describe the bug

The problem is as follows:

  • I want to write to a JSON object of the type Map<String, Object>, where the values are similar maps.
  • I want to have behavior similar to
var objectMapper = new ObjectMapper();  
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 

In other words, I should not write keys with null values, even in nested maps. BUT

  • I do not have access to the objectMapper instance, so the problem must be solved "externally."
  • And there are options for wrapper classes that can have annotations attached to them:
//????  
public class NonNullWrapper<K, V> extends HashMap<K, V> {  
	  //some constructors
}
//????  
public class AnotherNonNullWrapper {  
  
    @JsonValue  
    //????  
    private final Map<String, Object> value;  
  
    public AnotherNonNullWrapper(Map<String, Object> value) {  
        this.value = value;  
    }  
}

But when placing @JsonInclude(...NON_NULL) above the NonNullWrapper class, nulls are removed only at the top level. Jackson then sees regular maps in the values, which naturally don’t have annotations, and applies the default policy.

Similarly, Jackson ignores such annotations on a field marked with @JsonValue.
Inside Jackson's source code, during serialization (with @JsonValue), the JsonValueSerializer simply does not have information about the annotations that are attached to the marked field.

In the end, the question is: Is this behavior expected? Can this task be solved using annotations or other ways? However, I would prefer not to write my own JsonSerializer, Filter, or similar things for this.

Version Information

2.15.3 (but still fails on 2.18.0)

Reproduction

@JsonInclude(value = JsonInclude.Include.NON_NULL, content = JsonInclude.Include.NON_NULL)
public class NonNullWrapper<K, V> extends HashMap<K, V> {

    public NonNullWrapper(Map<K, V> map) {
        super(map);
    }
}

public class AnotherNonNullWrapper {

    @JsonValue
    @JsonInclude(value = JsonInclude.Include.NON_NULL, content = JsonInclude.Include.NON_NULL)
    private final Map<String, Object> value;

    public AnotherNonNullWrapper(Map<String, Object> value) {
        this.value = value;
    }
}


public static final TypeReference<Map<String, Object>> MAP_REF = new TypeReference<>() {};
    public static final TypeReference<List<Object>> LIST_REF = new TypeReference<>() {};

    @Test
    public void test() throws JsonProcessingException {
        var map = new HashMap<String, Object>();
        map.put("ignored", null);
        Map<String, Object> nested = new HashMap<>();
        nested.put("ignored", null);
        nested.put("array", List.of(map, map));
        Map<String, Object> topLevel = new HashMap<>();
        topLevel.put("ignored", null);
        topLevel.put("empty", "");
        topLevel.put("nested", nested);

        var expected = """
            {
              "nested": {
                "array": [
                  {
                  },
                  {
                  }
                ]
              },
              "empty": ""
            }
            """;

        var serialized = getDefaultMapper().writeValueAsString(new NonNullWrapper OR AnotherNonNullWrapper(topLevel));

        assertMapEqualsAfterSerialization(serialized, expected);
    }

    private void assertMapEqualsAfterSerialization(String mapAsJson, String expected) throws JsonProcessingException {
        assertEqualsAfterSerialization(mapAsJson, expected, MAP_REF);
    }

    private void assertEqualsAfterSerialization(String sourceJson, String expectedJson, TypeReference<?> tr)
        throws JsonProcessingException {
        var sourceMap = mapper.readValue(sourceJson, tr);
        var expectedMap = mapper.readValue(expectedJson, tr);

        var resultJson = mapper.writeValueAsString(sourceMap);
        var resultMap = mapper.readValue(resultJson, tr);

        assertThat(resultMap).isEqualTo(expectedMap);
    }

Results

NonNullWrapper

org.opentest4j.AssertionFailedError: 
expected: {"empty"="", "nested"={"array"=[{}, {}]}}
 but was: {"empty"="", "nested"={"array"=[{"ignored"=null}, {"ignored"=null}], "ignored"=null}}
Expected :{"empty"="", "nested"={"array"=[{}, {}]}}
Actual   :{"empty"="", "nested"={"array"=[{"ignored"=null}, {"ignored"=null}], "ignored"=null}}

AnotherNonNullWrapper

org.opentest4j.AssertionFailedError: 
expected: {"empty"="", "nested"={"array"=[{}, {}]}}
 but was: {"empty"="", "ignored"=null, "nested"={"array"=[{"ignored"=null}, {"ignored"=null}], "ignored"=null}}
Expected :{"empty"="", "nested"={"array"=[{}, {}]}}
Actual   :{"empty"="", "ignored"=null, "nested"={"array"=[{"ignored"=null}, {"ignored"=null}], "ignored"=null}}

Expected behavior

@JsonInclude annotation has effect on nested maps or/and @JsonValue marked field

Additional context

No response

@sikorskii sikorskii added the to-evaluate Issue that has been received but not yet evaluated label Oct 23, 2024
@JooHyukKim
Copy link
Member

Please try later versions of Jackson, like recently released 2.18 and please let us know!

I have a feeling nested deserialization of Map<String, Object> might not work.... 🤔

@sikorskii
Copy link
Author

@JooHyukKim I tried version 2.18.0 and behavior did not change

@JooHyukKim
Copy link
Member

I have a feeling nested deserialization of Map<String, Object> might not work.... 🤔

Okay, probably this is the cause. Will try to look into it later!
Thank you for reporting and providing reproduction tho

@cowtowncoder cowtowncoder added 2.18 and removed to-evaluate Issue that has been received but not yet evaluated labels Oct 24, 2024
@cowtowncoder
Copy link
Member

From quick look:

  1. Ideally, it should work, but
  2. I am not surprised it doesn't; contextual annotations probably are not applied to @JsonValue serialized values

So adding a failing test (under src/test/java/.../tofix) would make sense.

Fix is probably not trivial to do, although I could be wrong: contextualization should be done against accessor (Field, Getter) on which @JsonValue is applied. But there might not be BeanProperty created for it (as it's not really a property).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants