Reputation: 105
I have a method returning a custom Map implementation, whose entries consist entirely of non-null keys and values, so I would like to add type annotations in order to indicate that clients may iterate over the Map.Entry's without having to check them for null values: Map<@NonNull String,@NonNull String>
The problem is that the Map.get method API specifies that null be returned for any attempt to retrieve a value for a key which isn't present in the Map, and annotating my get method implementation to return a @Nullable String generates a compiler warning that the return type is then incompatible with the @NonNull return specified by the Map.
I understand that a Map.get API created today might perhaps return a java.util.Optional result or throw a NoSuchElementException, but, being beholden to the existing Collections API, is it possible to remain compliant with the get method specification and also specify that my Map contains only @NonNull values?
Much Thanks.
Upvotes: 3
Views: 4294
Reputation: 11
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(Include.NON_NULL);
When you have this error with null value or key:
c.f.j.c.JsonGenerationException:
Null key for a Map not allowed in JSON (use a converting NullKeySerializer?)
at c.f.j.d.s.i.FailingSerializer.serialize(FailingSerializer.java:36)
You solve override Serialize method like:
class MySerializer extends StdSerializer<Object> {
public MyDtoNullKeySerializer() {
this(null);}
public MySerializer(Class<Object> t) {
super(t);
}
@Override
public void serialize(Object nullKey, JsonGenerator jsonGenerator,SerializerProvider unused)
throws IOException, JsonProcessingException {
jsonGenerator.writeFieldName("");
}
}
For example complete see here: https://lentux-informatica.com/jackson-problema-con-maps-e-valori-null/
Upvotes: 0
Reputation: 165
Here's a simple library that can be used as a work around:
package xxxx;
import java.util.*;
import org.eclipse.jdt.annotation.*;
public class NullUtil
{
@SuppressWarnings ("null")
public static <@NonNull K, @NonNull T> @NonNull Optional<@Nullable T> get (@NonNull Map<@NonNull K, @NonNull T> m, @NonNull K key)
{
return Optional.ofNullable(m.get(key));
}
@SuppressWarnings ("null")
public static <@NonNull K, @NonNull T> @NonNull T getOrThrow (@NonNull Map<@NonNull K, @NonNull T> m, @NonNull K key)
{
return Optional.ofNullable(m.get(key)).orElseThrow(
() -> new RuntimeException("Map does not contain element '" + key + "'"));
}
@SuppressWarnings ("null")
public static <@NonNull K, @NonNull T> @Nullable T getOrNull (@NonNull Map<@NonNull K, @NonNull T> m, @NonNull K key)
{
return Optional.ofNullable(m.get(key)).orElse(null);
}
}
And here are some example uses:
package xxxx;
import static org.junit.Assert.*;
import java.util.*;
import org.eclipse.jdt.annotation.NonNull;
import org.junit.*;
public class NullUtilTest
{
@Before
public void setup ()
{
_m.put("a", "va");
_m.put("b", "vb");
_m.put("c", "vc");
}
@Test
@SuppressWarnings ("null")
public void testOptional ()
{
assertTrue(NullUtil.get(_m, "a").isPresent());
assertFalse(NullUtil.get(_m, "x").isPresent());
assertEquals("va", NullUtil.get(_m, "a").orElse(null));
assertEquals(null, NullUtil.get(_m, "x").orElse(null));
}
@Test
public void testOrNull ()
{
assertEquals("va", NullUtil.getOrNull(_m, "a"));
assertEquals(null, NullUtil.getOrNull(_m, "x"));
}
@Test
public void testOrThrow ()
{
assertEquals("va", NullUtil.getOrThrow(_m, "a"));
try
{
assertEquals("vx", NullUtil.getOrThrow(_m, "x"));
fail();
}
catch (Exception e)
{
assertEquals("Map does not contain element 'x'", e.getMessage());
}
}
private final @NonNull Map<@NonNull String, @NonNull String> _m = new HashMap<>();
}
Upvotes: 0
Reputation: 8117
You seem to be referring to Eclipse's implementation of nullity checking. You are right that it does not reason precisely about calls to Map.get
.
If more precise reasoning about calls to Map.get
is important to you, you might want to consider using a different nullity checker. For example, the Nullness Checker that is built on the Checker Framework handles your case. It treats the return value of Map.get
as non-null if the key is in the map and all the elements of the map are non-null.
Upvotes: 1
Reputation: 3453
Unfortunately, the Map API is not really compatible with null annotations. Map.get returns the generic type V, even when you define V to be @NonNull, which then violates the API since null must be an allowable return value.
This is a known limitation of null annotations, and will probably only be resolved when nullity profiles for libraries are implemented. Until then, the only workarounds are to check with Map.containsKey before getting a value instead of checking the value afterwards for null, or just avoid using @NonNull on map value types.
Upvotes: 3