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

InvalidDefinitionException when calling mapper.createObjectNode().putPOJO #3262

Closed
jebbench opened this issue Aug 27, 2021 · 9 comments
Closed
Labels
has-failing-test Indicates that there exists a test case (under `failing/`) to reproduce the issue
Milestone

Comments

@jebbench
Copy link

Hello,

When running the code below I get the exception:

Exception in thread "main" java.lang.RuntimeException: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.LocalDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling
	at com.fasterxml.jackson.databind.node.InternalNodeMapper.nodeToPrettyString(InternalNodeMapper.java:40)
	at com.fasterxml.jackson.databind.node.BaseJsonNode.toPrettyString(BaseJsonNode.java:141)
	at Main.main(Main.java:16)
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.LocalDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.LocalDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling

	at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77)
	at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1300)
	at com.fasterxml.jackson.databind.ser.impl.UnsupportedTypeSerializer.serialize(UnsupportedTypeSerializer.java:35)
	at com.fasterxml.jackson.databind.SerializerProvider.defaultSerializeValue(SerializerProvider.java:1142)
	at com.fasterxml.jackson.databind.node.POJONode.serialize(POJONode.java:115)
	at com.fasterxml.jackson.databind.node.ObjectNode.serialize(ObjectNode.java:328)
	at com.fasterxml.jackson.databind.ser.std.SerializableSerializer.serialize(SerializableSerializer.java:39)
	at com.fasterxml.jackson.databind.ser.std.SerializableSerializer.serialize(SerializableSerializer.java:20)
	at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480)
	at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:319)
	at com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1518)
	at com.fasterxml.jackson.databind.ObjectWriter._writeValueAndClose(ObjectWriter.java:1219)
	at com.fasterxml.jackson.databind.ObjectWriter.writeValueAsString(ObjectWriter.java:1086)
	at com.fasterxml.jackson.databind.node.InternalNodeMapper.nodeToPrettyString(InternalNodeMapper.java:38)
	... 2 more

2.11.4 works as expected.
2.13.0-rc2 fails with the same error.

Main.java

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

import java.time.LocalDateTime;

public class Main {

    public static void main(String[] args) {
        ObjectMapper mapper = JsonMapper.builder()
            .addModule(new JavaTimeModule())
            .build();

        ObjectNode node = mapper.createObjectNode().putPOJO("test", LocalDateTime.now());
        System.out.println(node.toPrettyString());
    }

}

build.gradle

plugins {
    id 'java'
}

group 'org.example'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    implementation platform("com.fasterxml.jackson:jackson-bom:2.12.5")
    implementation "com.fasterxml.jackson.core:jackson-databind"
    implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310"
}

I've tried using AdoptOpenJDK (OpenJ9) 11.0.11 and AdoptOpenJDK (Hotspot) 14.0.2 and I get the same result on both.

@jebbench jebbench changed the title InvalidDefinitionException: Java 8 date/time type java.time.LocalDateTime not supported by default when calling mapper.createObjectNode().putPOJO InvalidDefinitionException: Java 8 date/time type java.time.LocalDateTime... when calling mapper.createObjectNode().putPOJO Aug 27, 2021
@cowtowncoder
Copy link
Member

This is intentional: as per #2683, Java 8 date/time types cannot be serialized (as POJOs) since those cannot be read back (deserialized). The solution is to register module jackson-datatype-jsr353 (see https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-jsr353 or https://github.com/FasterXML/jackson-modules-java8/), which will add expected serializer.

@jebbench
Copy link
Author

I think I am adding the module:

ObjectMapper mapper = JsonMapper.builder()
            .addModule(new JavaTimeModule())
            .build();

It doesn't actually work in 2.11, it just serializes the date without using the module.

I looks like calling any accessor methods on ObjectNode ignores any modules loaded in the Object Mapper which used to create it.

My use case is in a unit test checking that the ObjectNode is built correctly - in running code it's not an issue as I always do objectMapper.writeValue but in my tests I just want to check the updatedAt key is correct (node.get("updatedAt")).

@cowtowncoder
Copy link
Member

cowtowncoder commented Sep 1, 2021

Looking at this, the actual problem is the ObjectNode.toString() -- it cannot serialize POJOs.
Proper way here would be to use ObjectMapper:

mapper.writeValueAsString(node);

The problem with toString() is that ObjectNode has no connection to real ObjectMapper, and that is why there's no way to add module.
Using a full ObjectMapper will resolve this issue.

I'll have to think about this for a bit; behavior is not optimal but I am not sure what could be done to improve it.

cowtowncoder added a commit that referenced this issue Sep 1, 2021
@jebbench
Copy link
Author

jebbench commented Sep 1, 2021

What would be the best way to retrieve a value added using putPOJO?

I have a unit test where I want to make sure that the value added at a particular key is the expected value (I can cope with getting back the POJO or the equivalent JSON value).

LocalDateTime now = LocalDateTime.now();
ObjectNode node = mapper.createObjectNode().putPOJO("test", now);

assertThat(node.get("test")).isEqualTo(now));
// or
assertThat(node.get("test")).isEqualTo(now.toString()));

Would it be possible to add a getPOJO method to allow access to the original POJO?

@cowtowncoder
Copy link
Member

cowtowncoder commented Sep 1, 2021

POJONode has method getPojo() -- but you need to cast the type from JsonNode to call it.

@liang-shen
Copy link

If the Temporal is created as POJONode, this exception will be thrown because of the BaseJsonNode::toString()

 @Override
   public String toString() {
       return InternalNodeMapper.nodeToString(this);
   }

@cowtowncoder cowtowncoder added the has-failing-test Indicates that there exists a test case (under `failing/`) to reproduce the issue label Aug 30, 2022
@cowtowncoder
Copy link
Member

At this point I can reproduce the issue but don't really know if anything may be done -- my suggestion is that when using "POJO" nodes, use mapper.writeValueAsString() and not rely on JsonNode.toString() as that can never really have support for external types.

The problem then being that of error handling as there seem to be only bad choices:

  1. Throw an unchecked exception (which is surprising from toString())
  2. Produce invalid JSON (like truncate, or add exception info)
  3. Produce valid JSON but one that is different from what one would get from proper serialization (like JSON String with error message)

cowtowncoder added a commit that referenced this issue Feb 8, 2023
@cowtowncoder cowtowncoder changed the title InvalidDefinitionException: Java 8 date/time type java.time.LocalDateTime... when calling mapper.createObjectNode().putPOJO InvalidDefinitionException when calling mapper.createObjectNode().putPOJO Feb 8, 2023
@cowtowncoder cowtowncoder modified the milestones: 2.10.0, 2.15.0 Feb 8, 2023
@cowtowncoder
Copy link
Member

Went with (3), catching serialization issue specifically for POJONode. WIll see whether this is the right call; only bad choices exist.

@jebbench
Copy link
Author

We've updated to 2.15 and we've been able to use a PojoNode containing a LocalDateTime in our tests without exceptions being thrown :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
has-failing-test Indicates that there exists a test case (under `failing/`) to reproduce the issue
Projects
None yet
Development

No branches or pull requests

3 participants