Skip to content

The ObjectMapper.readValue extension methods need the type param to be bound to Any #480

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Closed
ragnese opened this issue Jul 22, 2021 · 8 comments
Labels

Comments

@ragnese
Copy link

ragnese commented Jul 22, 2021

Describe the bug
One can violate the type system by deserializing null where it was not intended

To Reproduce

class Foo(val value: Int) // any class

val mapper = ObjectMapper().registerKotlinModule()

val deserialized = mapper.readValue<Foo>("null") // succeeds
println(deserialized) // null

Expected behavior
Deserialization should fail when the declared type parameter is not explicitly nullable

Versions
Kotlin: 1.4
Jackson-module-kotlin: 2.13
Jackson-databind: 2.13

Additional context
The fix is to add a type bound to the current methods and to add a new set of nullable methods:

inline fun <reified T: Any> ObjectMapper.readValue(content: String): T = readNullableValue(content)) ?:  throw SomeMappingException()

inline fun <reified T: Any> ObjectMapper.readNullableValue(content: String): T? = readValue(content, jacksonTypeRef<T>())
@ragnese ragnese added the bug label Jul 22, 2021
@dinomite
Copy link
Member

I think this is the same as #399.

Adding that readNullableValue() seems like a good idea, would you make a PR?

@ragnese
Copy link
Author

ragnese commented Jul 26, 2021

I'm working on the PR today.

One question is what exception should be thrown for the non-nullable versions when they deserialize a null?

@dinomite
Copy link
Member

Hmm, I'm not sure what is thrown when the existing strictNullChecks is violated—throwing the same exception is probably the right approach, though you could also create a new exception in Exception.kt if there doesn't seem to be a good one that already exists.

@ragnese
Copy link
Author

ragnese commented Jul 26, 2021

Okay. I'll look into what strictNullChecks throws. I was originally going to just throw a new DatabindException, but I wasn't sure how to implement it.

class DeserializedNullException() : DatabindException("Deserialized a null value") {
    override fun prependPath(referrer: Any?, fieldName: String?) {
        // TODO - I don't know what to do here.
    }

    override fun prependPath(referrer: Any?, index: Int) {
        // TODO - I don't know what to do here.
    }
}

Similarly, I didn't know how to throw a JsonMappingException because all of the non-deprecated ctors take a Closeable processor argument, which I won't have from just wrapping the readValue calls and checking for null.

@dinomite
Copy link
Member

Hmm, that's a new exception in 2.13, so I'm not sure—it looks like perhaps implementations are supposed to add the information passed to those functions to the exception message?

The only usage in jackson-databind accumulates into a List and _buildMessage() adds it to the exception message through this call to getPathReference().

All that is to say, it looks like extending JsonMappingException would be best or perhaps using ValueInstantiationException is appropriate.

@cowtowncoder
Copy link
Member

+1 for what @dinomite said.

One other quick note:

DatabindException is the "new" base type added to be used in 3.0: it will replace JsonMappingException there; with 2.x it is sort of redundant base type which CAN be used for receiving (that is, code may catch it), but CAN NOT be thrown (since it is abstract / incomplete)

So code in 2.x differs from 3.0 (master) here, unfortunately. The intent is, however, that the vast majority of code should not directly instantiate exceptions but either:

  1. Use methods in base (de)serializer/handler implementations (when extending)
  2. Use methods in context objects (DeserializationContext / SerializerProvider

which will properly attach necessary information, as well as have same (or, sometimes, similar) signature between 2.x and 3.x code bases.

I know this is a bit of hassle due to rather long development cycle for 3.0 (unintentional. due to 2.x lifetime exceeding initial expectations).

@revintec
Copy link

@dinomite Hi, I don't think adding a new extension method is the right approach, we should make the existing extension method work

  1. the old code using the new jackson library would work without modification/recompilation
  2. the new extension method is pointless, nullable/non-nullable is already baked in the language, we should not add user's mental burden

@k163377
Copy link
Contributor

k163377 commented Apr 16, 2023

This issue is closed as a duplicate of #399.

@k163377 k163377 closed this as completed Apr 16, 2023
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants