-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
FromStringDeserializer is trying to convert a FIELD_NAME token instead of a VALUE_STRING #4651
Comments
Hello @cowtowncoder ? How are you |
I'm not expecting you to remember me but we worked on #1295 long time ago. |
Hi there! Apologies for slow follow-up: just returned from my 2-week vacation. This seems quite involved; hoping to look into it soon. |
No worries. Thank you for acknowledging this. Take your time |
Ok; quick note: But it sounds more like either |
Ok I think |
Hi ! import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
import java.net.MalformedURLException;
import java.net.URL;
class Scratch {
@JsonTypeInfo(
use = JsonTypeInfo.Id.SIMPLE_NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "$$element",
visible = false
)
public @interface CustomAnnotation {
}
public static void main(String[] args) throws MalformedURLException, JsonProcessingException {
new Scratch().testDeserializeDesignSystemElements1();
;
}
@CustomAnnotation
static class HttpURI implements Scratch.URI {
private final URL url;
@JsonCreator
public HttpURI(URL url) {
this.url = url;
}
public String getScheme() {
return url.getProtocol();
}
public String getAuthority() {
return url.getAuthority();
}
public String getPath() {
return url.getPath();
}
public String getRelativePath() {
return getPath() + url.getQuery().replaceAll("\\?", "\\?") +
url.getRef().replaceAll("#", "\\#");
}
public String getValue() {
return url.toString();
}
}
@CustomAnnotation
interface URI {
public String getScheme();
public String getAuthority();
public String getPath();
default String getValue() {
return getScheme() + ":" + getAuthority().replaceAll("/", "\\/") + getPath();
}
}
void testDeserializeDesignSystemElements1() throws MalformedURLException, JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
String response =
"{\"url\":\"https://localhost\",\"$$element\":\"HttpURI\"}";
URI element = objectMapper.readValue(response, HttpURI.class); // failing
// Caused by: java.net.MalformedURLException: no protocol: url
// at java.base/java.net.URL.<init>(URL.java:674)
// at java.base/java.net.URL.<init>(URL.java:569)
// at java.base/java.net.URL.<init>(URL.java:516)
// at com.fasterxml.jackson.databind.deser.std.FromStringDeserializer$Std._deserialize(FromStringDeserializer.java:319)
Assertions.assertInstanceOf(HttpURI.class, element);
}
} |
How does |
@JsonTypeInfo(
use = JsonTypeInfo.Id.SIMPLE_NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "$$element",
visible = false
)
public @interface CustomAnnotation {
} in above example |
@JooHyukKim Conceptually that'd make sense, but nothing in code seems to make that happen. Maybe it's missing |
So I can apparently reduce the entropy to be just this : import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
import java.net.MalformedURLException;
import java.net.URL;
class Scratch {
public static void main(String[] args) throws MalformedURLException, JsonProcessingException {
new Scratch().testDeserializeDesignSystemElements();
}
static class HttpURI {
private final URL url;
@JsonCreator
public HttpURI(URL url) {
this.url = url;
}
}
void testDeserializeDesignSystemElements() throws MalformedURLException, JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
String response = "{\"url\":\"https://localhost\"}";
HttpURI element = objectMapper.readValue(response, HttpURI.class); // failing
Assertions.assertInstanceOf(HttpURI.class, element);
}
} When I use |
It might not be related to JsonSubTypes at all. |
so jackson tries to read it as a @JsonValue automatically ? |
I am sorry, but I am not that familiar with behaviors wrt And lemme know what you find! |
I don't want to get it parsed as |
No, unless we are talking about an Enum value. But it is indeed very likely you are missing
so Jackson didn't guess, incorrectly, that you want "DELEGATING" mode (it guess this way due to heuristics based and due to there not being getter or
|
Search before asking
Describe the bug
I'm using a JsonTypeInfo to tell the parser to look for sub types. There is a $$element property that matches the class simple name, so I'm using this type info mapping:
While I could see the practice working fine over a few examples, there was one case that resulted in systematic error :
Here is my json that I need to deserialize :
url
is of typejava.net.URL
.The execution runs the method
AsPropertyTypeDeserializer._deserializeTypedForId
(fromdeserializeTypedFromObject
, as soon as the type id property is scanned)jackson-databind/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsPropertyTypeDeserializer.java
Lines 148 to 171 in 92181df
a new
JsonParser
is created with the concatenation of the fields read prior to the type id identification and the remaining fields to be converted (i'm skipping the id$$element
since I'm not longer needing it). This is the result of the statementJsonParserSequence.createFlattened(false, tb.asParser(p), p);
AsPropertyTypeDeserializer does
p.nextToken()
once and now is pointing at the first token of the temporary TokenBuffer built to keep track of the read tokens. So it's pointing at FIELD_NAME.Since the concatenation starts with "fieldName" and "values", it is never starting with START_OBJECT, so the
BeanDeserializer.deserialize
will then attempt to_deserializeOther
, while the json parser is now pointing at the FIELD_NAMEThen while going to
BeanDeserializerBase.deserializeFromObjectUsingNonDefault(p, ctxt)
,delegateDeser
is resolved asFromStingDeserializer
, which will now be called... with a FIELD_NAME !jackson-databind/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java
Lines 1482 to 1490 in 92181df
FromStringDeserializer.deserialize
never tries to figure out that it's not pointing at the right location...So instead of using
https://localhost
to convert it into an URL, it uses the previous token...So it tries to convert
url
(the field name) into an URL, which of course fails.I'd suppose that
FromStringDeserializer.deserialize
should do :at the beginning of the method :
jackson-databind/src/main/java/com/fasterxml/jackson/databind/deser/std/FromStringDeserializer.java
Lines 146 to 170 in 92181df
But I don't want to overthink it, and you might know of any better solution rather than walking around the code mistake.
Version Information
2.17.2
Reproduction
<-- Any of the following
-->
// Your code here
Here is the definition for the HttpUri data class:
(the type info is defined using a JacksonAnnotationIntrospector for @CustomAnnotation, rather than using standard annotations)
here are the operations defined in the custom JacksonAnnotationInspector :
here is a unit test :
(toString is doing a conversion from object to JSX format for React)
error message was :
Expected behavior
The value
https://localhost
should be parsed as URL instead of the field nameurl
Additional context
Here is the workaround I'm adding. That's really not a great situation to work with :
The text was updated successfully, but these errors were encountered: