chore(tazjin): Move //web/blog & //web/homepage to my userdir
Change-Id: I96a2620ffb1d9e98a1d8ce7d97f2c4f58c2dbfd3 Reviewed-on: https://cl.tvl.fyi/c/depot/+/603 Reviewed-by: tazjin <mail@tazj.in>
This commit is contained in:
parent
a46ffd85f5
commit
a2cbbedc65
32 changed files with 0 additions and 0 deletions
98
users/tazjin/blog/posts/make-object-t-again.md
Normal file
98
users/tazjin/blog/posts/make-object-t-again.md
Normal file
|
@ -0,0 +1,98 @@
|
|||
A few minutes ago I found myself debugging a strange Java issue related
|
||||
to Jackson, one of the most common Java JSON serialization libraries.
|
||||
|
||||
The gist of the issue was that a short wrapper using some types from
|
||||
[Javaslang](http://www.javaslang.io/) was causing unexpected problems:
|
||||
|
||||
```java
|
||||
public <T> Try<T> readValue(String json, TypeReference type) {
|
||||
return Try.of(() -> objectMapper.readValue(json, type));
|
||||
}
|
||||
```
|
||||
|
||||
The signature of this function was based on the original Jackson
|
||||
`readValue` type signature:
|
||||
|
||||
```java
|
||||
public <T> T readValue(String content, TypeReference valueTypeRef)
|
||||
```
|
||||
|
||||
While happily using my wrapper function I suddenly got an unexpected
|
||||
error telling me that `Object` is incompatible with the type I was
|
||||
asking Jackson to de-serialize, which got me to re-evaluate the above
|
||||
type signature again.
|
||||
|
||||
Lets look for a second at some code that will *happily compile* if you
|
||||
are using Jackson\'s own `readValue`:
|
||||
|
||||
```java
|
||||
// This shouldn't compile!
|
||||
Long l = objectMapper.readValue("\"foo\"", new TypeReference<String>(){});
|
||||
```
|
||||
|
||||
As you can see there we ask Jackson to decode the JSON into a `String`
|
||||
as enclosed in the `TypeReference`, but assign the result to a `Long`.
|
||||
And it compiles. And it failes at runtime with
|
||||
`java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Long`.
|
||||
Huh?
|
||||
|
||||
Looking at the Jackson `readValue` implementation it becomes clear
|
||||
what\'s going on here:
|
||||
|
||||
```java
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public <T> T readValue(String content, TypeReference valueTypeRef)
|
||||
throws IOException, JsonParseException, JsonMappingException
|
||||
{
|
||||
return (T) _readMapAndClose(/* whatever */);
|
||||
}
|
||||
```
|
||||
|
||||
The function is parameterised over the type `T`, however the only place
|
||||
where `T` occurs in the signature is in the parameter declaration and
|
||||
the function return type. Java will happily let you use generic
|
||||
functions and types without specifying type parameters:
|
||||
|
||||
```java
|
||||
// Compiles fine!
|
||||
final List myList = List.of(1,2,3);
|
||||
|
||||
// Type is now myList : List<Object>
|
||||
```
|
||||
|
||||
Meaning that those parameters default to `Object`. Now in the code above
|
||||
Jackson also explicitly casts the return value of its inner function
|
||||
call to `T`.
|
||||
|
||||
What ends up happening is that Java infers the expected return type from
|
||||
the context of the `readValue` and then happily uses the unchecked cast
|
||||
to fit that return type. If the type hints of the context aren\'t strong
|
||||
enough we simply get `Object` back.
|
||||
|
||||
So what\'s the fix for this? It\'s quite simple:
|
||||
|
||||
```java
|
||||
public <T> T readValue(String content, TypeReference<T> valueTypeRef)
|
||||
```
|
||||
|
||||
By also making the parameter appear in the `TypeReference` we \"bind\"
|
||||
`T` to the type enclosed in the type reference. The cast can then also
|
||||
safely be removed.
|
||||
|
||||
The cherries on top of this are:
|
||||
|
||||
1. `@SuppressWarnings({ "rawtypes" })` explicitly disables a
|
||||
warning that would\'ve caught this
|
||||
|
||||
2. the `readValue` implementation using the less powerful `Class`
|
||||
class to carry the type parameter does this correctly: `public <T>
|
||||
T readValue(String content, Class<T> valueType)`
|
||||
|
||||
The big question I have about this is *why* does Jackson do it this way?
|
||||
Obviously the warning did not just appear there by chance, so somebody
|
||||
must have thought about this?
|
||||
|
||||
If anyone knows what the reason is, I\'d be happy to hear from you.
|
||||
|
||||
PS: Shoutout to David & Lucia for helping me not lose my sanity over
|
||||
this.
|
Loading…
Add table
Add a link
Reference in a new issue