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

Allow @JsonAnySetter on Creator constructors #4366

Closed
wants to merge 72 commits into from
Closed
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
94540f8
temporarily remove other jdk versions
JooHyukKim Jan 27, 2024
9c6cace
Merge branch '2.17' into 562-jsonanysetter-test
JooHyukKim Jan 28, 2024
1faccf0
Working solution first draft
JooHyukKim Feb 3, 2024
8930a4e
Revert "temporarily remove other jdk versions"
JooHyukKim Feb 3, 2024
5280004
Delete JsonAnySetterThroughCreator562Test.java
JooHyukKim Feb 3, 2024
99a882b
Remove hard coded field names
JooHyukKim Feb 3, 2024
a840e56
Clean Up
JooHyukKim Feb 3, 2024
75c23f1
Clean up unused _anySetter
JooHyukKim Feb 3, 2024
2b7d22f
Add back required property _anySetter implementation
JooHyukKim Feb 3, 2024
2f49175
Merge branch '2.17' into 562-jsonanysetter-test
JooHyukKim Feb 3, 2024
1a9afab
Add `Boolean _ hasAnySetter` in
JooHyukKim Feb 4, 2024
2b2e0aa
Use hasAnySetter from CreatorProperty
JooHyukKim Feb 4, 2024
6a892f8
Remove unncessary anySetter construction
JooHyukKim Feb 4, 2024
765ed3a
Revert "Remove unncessary anySetter construction"
JooHyukKim Feb 4, 2024
57d467a
Put back isAnySetterXxx methods and fields
JooHyukKim Feb 4, 2024
2ae2dca
Fix JDK 8 error
JooHyukKim Feb 4, 2024
c4657cc
Merge branch '2.17' into 562-jsonanysetter-test
JooHyukKim Feb 9, 2024
7da980a
Move test dir
JooHyukKim Feb 9, 2024
09d4c82
Fix error
JooHyukKim Feb 9, 2024
9baad77
Add new SettableAnyProperty impl
JooHyukKim Feb 9, 2024
81a8803
Remove hard coded prop name
JooHyukKim Feb 9, 2024
742a589
Update javadoc and clean comments
JooHyukKim Feb 9, 2024
19182fa
Merge branch '2.17' into 562-jsonanysetter-test
cowtowncoder Feb 10, 2024
010849a
Minor clean up, streamlining
cowtowncoder Feb 10, 2024
10540a8
Add verification there's only one any-setter
cowtowncoder Feb 10, 2024
1458882
Test case for disabled(@JsonAnySetter) (rain check?
JooHyukKim Feb 10, 2024
bddb042
Require non-null _valueInstantiator during construction
JooHyukKim Feb 10, 2024
5c1f30d
Add test with DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES
JooHyukKim Feb 10, 2024
bdc2357
Fix tests
JooHyukKim Feb 10, 2024
47ac3f7
Update AnySetterForCreator562Test.java
JooHyukKim Feb 10, 2024
8612433
Implement version 2
JooHyukKim Feb 10, 2024
5d69465
Clean up changes
JooHyukKim Feb 10, 2024
551a001
Merge branch '2.17' into 562-jsonanysetter-test
JooHyukKim Feb 12, 2024
7d7a4c5
Merge branch '2.17' into 562-jsonanysetter-test
cowtowncoder Feb 19, 2024
778ddc3
Merge branch '2.17' into 562-jsonanysetter-test
JooHyukKim Apr 8, 2024
30c0f57
Merge branch '2.17' into 562-jsonanysetter-test
cowtowncoder May 31, 2024
7a55942
temporarily remove other jdk versions
JooHyukKim Jan 27, 2024
ff22500
Working solution first draft
JooHyukKim Feb 3, 2024
e2670e6
Revert "temporarily remove other jdk versions"
JooHyukKim Feb 3, 2024
05a4c0b
Delete JsonAnySetterThroughCreator562Test.java
JooHyukKim Feb 3, 2024
dd53092
Remove hard coded field names
JooHyukKim Feb 3, 2024
49f8b95
Clean Up
JooHyukKim Feb 3, 2024
b4d03e3
Clean up unused _anySetter
JooHyukKim Feb 3, 2024
426f303
Add back required property _anySetter implementation
JooHyukKim Feb 3, 2024
201c5ea
Add `Boolean _ hasAnySetter` in
JooHyukKim Feb 4, 2024
a247d02
Use hasAnySetter from CreatorProperty
JooHyukKim Feb 4, 2024
2ece9f9
Remove unncessary anySetter construction
JooHyukKim Feb 4, 2024
d3d8546
Revert "Remove unncessary anySetter construction"
JooHyukKim Feb 4, 2024
3d632f4
Put back isAnySetterXxx methods and fields
JooHyukKim Feb 4, 2024
ce58b14
Fix JDK 8 error
JooHyukKim Feb 4, 2024
21a0795
Move test dir
JooHyukKim Feb 9, 2024
09296d1
Fix error
JooHyukKim Feb 9, 2024
3445ddd
Add new SettableAnyProperty impl
JooHyukKim Feb 9, 2024
9e95155
Remove hard coded prop name
JooHyukKim Feb 9, 2024
1908532
Update javadoc and clean comments
JooHyukKim Feb 9, 2024
659bbf2
Minor clean up, streamlining
cowtowncoder Feb 10, 2024
fcfac63
Add verification there's only one any-setter
cowtowncoder Feb 10, 2024
f6a6c37
Test case for disabled(@JsonAnySetter) (rain check?
JooHyukKim Feb 10, 2024
93b8587
Require non-null _valueInstantiator during construction
JooHyukKim Feb 10, 2024
134d8d1
Add test with DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES
JooHyukKim Feb 10, 2024
7c60905
Fix tests
JooHyukKim Feb 10, 2024
3e62a2b
Update AnySetterForCreator562Test.java
JooHyukKim Feb 10, 2024
60a2933
Implement version 2
JooHyukKim Feb 10, 2024
40061f9
Clean up changes
JooHyukKim Feb 10, 2024
cd86a74
Add missing imports
JooHyukKim May 31, 2024
6cbdeff
Merge branch '562-jsonanysetter-test' of https://github.com/JooHyukKi…
JooHyukKim May 31, 2024
8046794
Merge branch '2.18' into 562-jsonanysetter-test
cowtowncoder May 31, 2024
2de9beb
Clean up code, version, etc...
JooHyukKim May 31, 2024
825f31e
Merge branch '2.18' into 562-jsonanysetter-test
cowtowncoder May 31, 2024
cdcdf3e
Merge branch '2.18' into 562-jsonanysetter-test
cowtowncoder May 31, 2024
2cd2b9d
Apply review
JooHyukKim Jun 1, 2024
b616cb6
Change initMap() method to createAnyPropertyMap()
JooHyukKim Jun 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -879,8 +879,14 @@ protected void _addExplicitPropertyCreator(DeserializationContext ctxt,
*/
}
name = candidate.findImplicitParamName(i);
_validateNamedPropertyParameter(ctxt, beanDesc, candidate, i,
// [databind#562] Any setter can be used...
Boolean hasAnySetter = ctxt.getAnnotationIntrospector().hasAnySetter(param);
if (hasAnySetter != null && hasAnySetter) {
JooHyukKim marked this conversation as resolved.
Show resolved Hide resolved
// no-op
} else {
_validateNamedPropertyParameter(ctxt, beanDesc, candidate, i,
name, injectId);
}
}
properties[i] = constructCreatorProperty(ctxt, beanDesc, name, i, param, injectId);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri
// "any property"?
if (_anySetter != null) {
try {
buffer.bufferAnyProperty(_anySetter, propName, _anySetter.deserialize(p, ctxt));
buffer.bufferMapProperty(propName, _anySetter.deserialize(p, ctxt));
} catch (Exception e) {
wrapAndThrow(e, _beanType.getRawClass(), propName, ctxt);
}
Expand All @@ -523,10 +523,15 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri
// We hit END_OBJECT, so:
Object bean;
try {
bean = creator.build(ctxt, buffer);
if (_anySetter != null) {
bean = creator.buildSimple(ctxt, buffer);
} else {
bean = creator.build(ctxt, buffer);
}
} catch (Exception e) {
return wrapInstantiationProblem(e, ctxt);
}

// 13-Apr-2020, tatu: [databind#2678] need to handle injection here
if (_injectables != null) {
injectValues(ctxt, bean);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -544,8 +544,11 @@ protected void addBeanProps(DeserializationContext ctxt,

// Also, do we have a fallback "any" setter?
AnnotatedMember anySetter = beanDesc.findAnySetterAccessor();
AnnotatedMember creatorPropWithAnySetter = _findCreatorPropWithAnySetter(ctxt, creatorProps);
if (anySetter != null) {
builder.setAnySetter(constructAnySetter(ctxt, beanDesc, anySetter));
} else if (creatorPropWithAnySetter != null) {
builder.setAnySetter(constructAnySetter(ctxt, beanDesc, creatorPropWithAnySetter));
} else {
// 23-Jan-2018, tatu: although [databind#1805] would suggest we should block
// properties regardless, for now only consider unless there's any setter...
Expand Down Expand Up @@ -661,6 +664,23 @@ protected void addBeanProps(DeserializationContext ctxt,
}
}

private AnnotatedMember _findCreatorPropWithAnySetter(DeserializationContext ctxt, SettableBeanProperty[] creatorProps) {
if (creatorProps != null) {
AnnotationIntrospector ai = ctxt.getAnnotationIntrospector();
JooHyukKim marked this conversation as resolved.
Show resolved Hide resolved
for (SettableBeanProperty prop : creatorProps) {
AnnotatedMember m = prop.getMember();
if (m != null) {
Boolean hasAnySetter = ai.hasAnySetter(m);
if (hasAnySetter != null && hasAnySetter) {
cowtowncoder marked this conversation as resolved.
Show resolved Hide resolved
return prop.getMember();
}
}

}
}
return null;
}

private boolean _isSetterlessType(Class<?> rawType) {
// May also need to consider getters
// for Map/Collection properties; but with lowest precedence
Expand Down Expand Up @@ -819,6 +839,22 @@ protected SettableAnyProperty constructAnySetter(DeserializationContext ctxt,
valueType, null, mutator,
PropertyMetadata.STD_OPTIONAL);

} else if (mutator instanceof AnnotatedParameter){
AnnotatedParameter af = (AnnotatedParameter) mutator;
// get the type from the content type of the map object
JavaType fieldType = af.getType();
// 31-Jul-2022, tatu: Not just Maps any more but also JsonNode, so:
if (fieldType.isMapLikeType()) {
fieldType = resolveMemberAndTypeAnnotations(ctxt, mutator, fieldType);
keyType = fieldType.getKeyType();
valueType = fieldType.getContentType();
prop = new BeanProperty.Std(PropertyName.construct("stuff"),
JooHyukKim marked this conversation as resolved.
Show resolved Hide resolved
fieldType, null, mutator, PropertyMetadata.STD_OPTIONAL);
} else {
return ctxt.reportBadDefinition(beanDesc.getType(), String.format(
"Unsupported type for any-setter: %s -- only support `Map`s, `JsonNode` and `ObjectNode` ",
ClassUtil.getTypeDescription(fieldType)));
}
} else if (isField) {
AnnotatedField af = (AnnotatedField) mutator;
// get the type from the content type of the map object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,12 @@ public PropertyValueBuffer startBuilding(JsonParser p, DeserializationContext ct
return new PropertyValueBuffer(p, ctxt, _propertyCount, oir);
}

public Object buildSimple(DeserializationContext ctxt, PropertyValueBuffer buffer) throws IOException
{
return _valueInstantiator.createFromObjectWith(ctxt,
_allProperties, buffer);
}

public Object build(DeserializationContext ctxt, PropertyValueBuffer buffer) throws IOException
{
Object bean = _valueInstantiator.createFromObjectWith(ctxt,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

import java.io.IOException;
import java.util.BitSet;
import java.util.HashMap;
import java.util.Map;

import com.fasterxml.jackson.core.JsonParser;

import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.SettableAnyProperty;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.deser.UnresolvedForwardReference;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;

/**
Expand Down Expand Up @@ -176,6 +179,34 @@ public Object[] getParameters(SettableBeanProperty[] props)
}
}
}

// [databind#562]: Respect @JsonAnySetter in @JsonCreator
// check if we have anySetter Param
AnnotationIntrospector ai = _context.getAnnotationIntrospector();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alas, this is not acceptable from performance perspective: all annotation introspection must occur when constructing deserializers and related handlers. It should be possible to change handling to keep track of information about existence of "any setter", instead of trying to introspect for every deserialization call.

for (int i = 0; i < props.length; i++) {
SettableBeanProperty prop = props[i];
AnnotatedMember member = prop.getMember();
if (member == null) {
continue;
}
Boolean hasAnySetter = ai.hasAnySetter(member);
if (hasAnySetter == null || !hasAnySetter) {
continue;
}
JooHyukKim marked this conversation as resolved.
Show resolved Hide resolved
// So we have prop with anySetter. Should be Map-like, so let's assign such?
// Assign all remaining values to the map
Map<String, Object> param = new HashMap<>();
for (PropertyValue next = buffered(); next != null; next = next.next) {
try {
next.assign(param);
} catch (IOException e) {
_context.reportInputMismatch(prop, e.getMessage());
}
}
// assign it then return
_creatorParameters[i] = param;
break;
}
return _creatorParameters;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.fasterxml.jackson.failing;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -36,12 +36,16 @@ public POJO562(@JsonProperty("a") String a,
@Test
public void testAnySetterViaCreator562() throws Exception
{
Map<String, Object> expected = new HashMap<>();
expected.put("b", Integer.valueOf(42));
expected.put("c", Integer.valueOf(111));

POJO562 pojo = MAPPER.readValue(a2q(
"{'a':'value', 'b':42}"
"{'a':'value', 'b':42, 'c': 111}"
),
POJO562.class);
assertEquals(pojo.a, "value");
assertEquals(Collections.singletonMap("b", Integer.valueOf(42)),
pojo.stuff);

assertEquals("value", pojo.a);
assertEquals(expected, pojo.stuff);
}
}