UnknownTypeIdCachingTest.java
package tools.jackson.databind.jsontype;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import tools.jackson.databind.*;
import tools.jackson.databind.annotation.JsonTypeIdResolver;
import tools.jackson.databind.jsontype.impl.TypeIdResolverBase;
import tools.jackson.databind.testutil.DatabindTestUtil;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
// [databind#6203]
/**
* Verifies caching of type ids in the type id lookup cache of the (long-lived,
* shared) {@link TypeDeserializer}: unresolvable ids handled via the "nullifying"
* fallback (whose use depends on reader configuration) are not retained, whereas
* ones handled via {@code defaultImpl} are, like resolvable type ids (cache being
* bounded in size).
*/
public class UnknownTypeIdCachingTest extends DatabindTestUtil
{
static final AtomicInteger RESOLVE_COUNT = new AtomicInteger();
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY,
property = "type", defaultImpl = DefaultAnimal.class)
@JsonTypeIdResolver(CountingIdResolver.class)
static abstract class Animal {
public String name;
}
static class Dog extends Animal { }
static class DefaultAnimal extends Animal { }
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY,
property = "type")
@JsonTypeIdResolver(CountingIdResolver.class)
static abstract class Animal2 {
public String name;
}
static class Dog2 extends Animal2 { }
static class Wrapper {
public Animal animal;
}
static class Wrapper2 {
public Animal2 animal;
}
public static class CountingIdResolver extends TypeIdResolverBase
{
@Override
public JsonTypeInfo.Id getMechanism() { return JsonTypeInfo.Id.NAME; }
@Override
public String idFromValue(DatabindContext ctxt, Object value) {
return idFromValueAndType(ctxt, value, value.getClass());
}
@Override
public String idFromValueAndType(DatabindContext ctxt, Object value, Class<?> type) {
return type.getSimpleName();
}
@Override
public JavaType typeFromId(DatabindContext ctxt, String id) {
RESOLVE_COUNT.incrementAndGet();
if ("Dog".equals(id)) {
return ctxt.constructType(Dog.class);
}
if ("Dog2".equals(id)) {
return ctxt.constructType(Dog2.class);
}
return null;
}
}
private final ObjectMapper MAPPER = newJsonMapper();
// Unknown type ids resolved via `defaultImpl` ARE cached (bounded by cache size
// limit), as `defaultImpl` does not depend on reader configuration
@Test
public void cachingOfUnknownIdsWithDefaultImpl() throws Exception
{
RESOLVE_COUNT.set(0);
ObjectReader r = MAPPER.readerFor(Wrapper.class);
for (int i = 0; i < 10; ++i) {
Wrapper w = r.readValue(a2q(
"{'animal':{'type':'bogus-"+i+"','name':'Rex'}}"));
assertInstanceOf(DefaultAnimal.class, w.animal);
}
// Every distinct unknown id has to be resolved once
assertEquals(10, RESOLVE_COUNT.get());
// ... and repeating a single unknown id resolves it just once, too
RESOLVE_COUNT.set(0);
for (int i = 0; i < 10; ++i) {
Wrapper w = r.readValue(a2q("{'animal':{'type':'bogus','name':'Rex'}}"));
assertInstanceOf(DefaultAnimal.class, w.animal);
}
assertEquals(1, RESOLVE_COUNT.get());
// Whereas known type ids ARE cached: resolved just once
RESOLVE_COUNT.set(0);
for (int i = 0; i < 10; ++i) {
Wrapper w = r.readValue(a2q("{'animal':{'type':'Dog','name':'Rex'}}"));
assertInstanceOf(Dog.class, w.animal);
assertEquals("Rex", w.animal.name);
}
assertEquals(1, RESOLVE_COUNT.get());
}
// Unknown type ids must not be cached: `FAIL_ON_INVALID_SUBTYPE` disabled variant
@Test
public void noCachingOfUnknownIdsWhenNullifying() throws Exception
{
RESOLVE_COUNT.set(0);
ObjectReader r = MAPPER.readerFor(Wrapper2.class)
.without(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE);
for (int i = 0; i < 10; ++i) {
Wrapper2 w = r.readValue(a2q(
"{'animal':{'type':'bogus-"+i+"','name':'Rex'}}"));
assertNull(w.animal);
}
assertEquals(10, RESOLVE_COUNT.get());
RESOLVE_COUNT.set(0);
for (int i = 0; i < 10; ++i) {
r.readValue(a2q("{'animal':{'type':'bogus','name':'Rex'}}"));
}
assertEquals(10, RESOLVE_COUNT.get());
RESOLVE_COUNT.set(0);
for (int i = 0; i < 10; ++i) {
Wrapper2 w = r.readValue(a2q("{'animal':{'type':'Dog2','name':'Rex'}}"));
assertInstanceOf(Dog2.class, w.animal);
}
assertEquals(1, RESOLVE_COUNT.get());
}
}