-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Description
Search before asking
- I searched in the issues and found nothing similar.
Describe the bug
Deserialization fails with InvalidDefinitionException: Cannot construct instance of AuditEvent (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
Version Information
3.0.3
Reproduction
The Test attempts to serialize and deserialize the AuditEvent class described below but fails on Deserialization with:
tools.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of AuditEvent` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (String)"{"timestamp":"2026-01-17T21:00:44.532975Z","principal":"user","type":"type","data":{"keyA":"ValueA","KeyB":"ValueB"}}"; line: 1, column: 2]
at tools.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:70)
at tools.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1950)
at tools.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:448)
at tools.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1480)
at tools.jackson.databind.deser.bean.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1432)
at tools.jackson.databind.deser.bean.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:480)
at tools.jackson.databind.deser.bean.BeanDeserializer.deserialize(BeanDeserializer.java:200)
at tools.jackson.databind.deser.DeserializationContextExt.readRootValue(DeserializationContextExt.java:265)`
Class for Serialization/Deserialization tests:
/**
* Copied from Spring Boot 4.0.0 for testing purposes.
* {@link org.springframework.boot.actuate.audit.AuditEvent}
* Simplified version for Jackson3 Deserializer issues.
*/
public class AuditEvent implements Serializable {
private final Instant timestamp;
private final String principal;
private final String type;
private final Map<String, @Nullable Object> data;
/**
* Create a new audit event for the current time from data provided as name-value
* pairs.
* @param principal the user principal responsible
* @param type the event type
* @param data the event data in the form 'key=value' or simply 'key'
*/
public AuditEvent(@Nullable String principal, String type, String... data) {
this(Instant.now(), principal, type, convert(data));
}
/**
* Create a new audit event.
* @param timestamp the date/time of the event
* @param principal the user principal responsible
* @param type the event type
* @param data the event data
*/
public AuditEvent(Instant timestamp, @Nullable String principal, String type, Map<String, @Nullable Object> data) {
Assert.notNull(timestamp, "'timestamp' must not be null");
Assert.notNull(type, "'type' must not be null");
this.timestamp = timestamp;
this.principal = (principal != null) ? principal : "";
this.type = type;
this.data = Collections.unmodifiableMap(data);
}
private static Map<String, @Nullable Object> convert(String[] data) {
Map<String, @Nullable Object> result = new HashMap<>();
for (String entry : data) {
int index = entry.indexOf('=');
if (index != -1) {
result.put(entry.substring(0, index), entry.substring(index + 1));
}
else {
result.put(entry, null);
}
}
return result;
}
............................The Test:
@ActiveProfiles("test")
@SpringJUnitConfig({JacksonConfig.class})
@WebMvcTest
public class AuditEventDeserializeMvcItVal {
@Autowired
private JsonMapper jsonMapper;
private AuditEvent startAuditEvent;
private String startAuditEventJson;
private String startAuditEventString;
private JsonMapper jsonMapperManual;
@BeforeEach
void serialize(){
startAuditEvent = new AuditEvent("user", "type", "keyA=ValueA","KeyB=ValueB");
startAuditEventJson = jsonMapper.writeValueAsString(startAuditEvent);
startAuditEventString = startAuditEvent.toString();
System.out.println("Starting AuditEvent Json: %s".formatted(startAuditEventJson));
System.out.println("Starting AuditEvent String: %s".formatted(startAuditEventString));
jsonMapperManual =
JsonMapper
.builder()
.enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION)
.constructorDetector(ConstructorDetector.USE_PROPERTIES_BASED)
.build();
}
@Test
void deserializeWithSpringMapper() {
AuditEvent auditEvent =
jsonMapper.readValue(
startAuditEventJson,
AuditEvent.class);
Assertions.assertNotNull(auditEvent);
Assertions.assertEquals("user", auditEvent.getPrincipal());
Assertions.assertEquals("type", auditEvent.getType());
Assertions.assertEquals(2, auditEvent.getData().size());
}
@Test
void deserializeWithLocalMapperDef() {
AuditEvent auditEvent =
jsonMapperManual.readValue(
startAuditEventJson,
AuditEvent.class);
Assertions.assertNotNull(auditEvent);
Assertions.assertEquals("user", auditEvent.getPrincipal());
Assertions.assertEquals("type", auditEvent.getType());
Assertions.assertEquals(2, auditEvent.getData().size());
}
}Expected behavior
AuditEvent should be constructed as a public constructor for the properties supplied exists.
Additional context
The defaut builder(which was always enough in Jackson2) fails but I have a Spring config which fails as well.
@Bean
public JsonMapperBuilderCustomizer includeSourceInLocationCustomizer() {
return builder -> {
builder.enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION);
builder.constructorDetector(
ConstructorDetector.USE_PROPERTIES_BASED
.withAllowImplicitWithDefaultConstructor(ConstructorDetector.DEFAULT_ALLOW_IMPLICIT_WITH_DEFAULT_CONSTRUCTOR)
.withRequireAnnotation(false)
.withAllowJDKTypeConstructors(true)
.withSingleArgMode(ConstructorDetector.SingleArgConstructor.PROPERTIES));
};
}