Search before asking
- [x] I searched in the issues and found nothing similar.
Describe the bug
Continuation of https://github.com/FasterXML/jackson-databind/issues/4922.
Version Information
2.19.1, 2.19.2, 2.18.3
Reproduction
When the containing class of the Map
is constructed with a @JsonCreator
constructor, the issue comes back.
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonMerge;
import com.fasterxml.jackson.core.JsonParser.Feature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
interface MyMap<K, V> extends Map<K, V> {}
class MapImpl<K, V> extends HashMap<K, V> implements MyMap<K, V> {}
class MapUtils {
static <K, V> MyMap<K, V> createMyMap() {
return new MapImpl<K, V>();
}
}
class MergeMap {
@JsonCreator
MergeMap(int inter, String s) {
System.out.println("creator for " + map.getClass().getSimpleName());
this.inter = inter;
this.s = s;
}
int inter;
String s;
public int getInter() {
return inter;
}
// public String getS() {
// return s;
// }
@JsonMerge
MyMap<Integer, String> map = MapUtils.createMyMap();
public MyMap<Integer, String> getMap() {
System.out.println("getMap");
return map;
}
@Override
public String toString() {
return map.toString() + " " + inter + " " + s;
}
public static void main(String[] args) throws Exception {
JsonMapper MAPPER = JsonMapper.builder()
.addModule(new ParameterNamesModule())
.configure(Feature.INCLUDE_SOURCE_IN_LOCATION, true)
.build();
var merge = new MergeMap(5, "f");
merge.getMap().put(3, "ADS");
System.out.println(merge);
System.out.println(" == serializing --");
var string = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(merge);
System.out.println(string);
System.out.println(" == deserializing --");
var merge2 = MAPPER.readValue(string, MergeMap.class);
System.out.println(" == checking --");
System.out.println(merge2);
}
}
Gives
creator for MapImpl
getMap
{3=ADS} 5 f
== serializing --
getMap
{
"inter" : 5,
"map" : {
"3" : "ADS"
}
}
== deserializing --
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `example.MyMap` (no Creators, like default constructor, exist): no default constructor found
at [Source: (String)"{
"inter" : 5,
"map" : {
"3" : "ADS"
}
}"; line: 3, column: 11] (through reference chain: example.MergeMap["map"])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1915)
at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:415)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1402)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:440)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:31)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:543)
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:587)
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:480)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1499)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:340)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:177)
at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:342)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4971)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3887)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3855)
at example.MergeMap.main(MergeMap.java:74)
Uncommenting the getS()
method resolves the issue, but shouldn't have affected the construction of the map.
Also, declaring Map<Integer, String> map = MapUtils.createMyMap();
instead also works, but the whole point is to merge to an initialized custom type. It shows that the containing class construction is valid, however.
Expected behavior
No response
Additional context
No response
Comment From: cowtowncoder
Interestingly enough, dropping second Constructor argument (String s
) also makes test pass.
Very strange.
Comment From: cowtowncoder
I suspect there's some code path for "incomplete" set of Creator parameters that is wrong... Because also passing JSON with value for "s" makes test pass.
Comment From: nlisker
Then the problem probably exists for custom Collection
types as well even with https://github.com/FasterXML/jackson-databind/issues/4783 fixed.
Comment From: cowtowncoder
@nlisker Yes, most likely. Technically this is quite different root cause, but from user POV similar. On plus side Collection
and Map
type fix here is probably same, if it's due to buffering.
Comment From: cowtowncoder
Yikes. Thinking this through, I realized the problem. The problem is this: MergeMap
instance does not exist until its Constructor is called, so map
property cannot be accessed before that. JSON content gets buffered and... I am not sure how all state handling goes then.
Will need to tackle this later on, right now I am bit stuck.
Comment From: cowtowncoder
Update: yes, buffering is problematic: only in case of every CreatorProperty
being found before anything to merge are things ok. Otherwise to-merge property, "map" needs to be buffered.
But by default we actually try deserializing all such properties (instead of buffering raw token stream for later deserialization) which for to-merge-properties calls wrong method as we do not yet have thing to merge with.
What would be needed is different buffering of content stream and deferred deserialization. Will need to see how feasible that is.
Comment From: nlisker
Will test on the next release.
Comment From: cowtowncoder
Turned out neatly solvable -- and even lead to surprise fix for #2692 !