Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,13 @@ protected SettableBeanProperty constructCreatorProperty(DeserializationContext c
SettableBeanProperty prop = CreatorProperty.construct(name, type, property.getWrapperName(),
typeDeser, beanDescRef.getClassAnnotations(), param, index, injectable,
metadata);
// [databind#1516]: Handle @JsonManagedReference for creator properties
if (intr != null) {
AnnotationIntrospector.ReferenceProperty ref = intr.findReferenceType(config, param);
if (ref != null && ref.isManagedReference()) {
prop.setManagedReferenceName(ref.getName());
}
}
ValueDeserializer<?> deser = findDeserializerFromAnnotation(ctxt, param);
if (deser == null) {
deser = (ValueDeserializer<?>) type.getValueHandler();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@
import tools.jackson.databind.deser.ReadableObjectId.Referring;
import tools.jackson.databind.deser.SettableBeanProperty;
import tools.jackson.databind.deser.UnresolvedForwardReference;
import tools.jackson.databind.deser.impl.ExternalTypeHandler;
import tools.jackson.databind.deser.impl.MethodProperty;
import tools.jackson.databind.deser.impl.ObjectIdReader;
import tools.jackson.databind.deser.impl.UnwrappedPropertyHandler;
import tools.jackson.databind.deser.impl.*;
import tools.jackson.databind.util.ClassUtil;
import tools.jackson.databind.util.IgnorePropertiesUtil;
import tools.jackson.databind.util.NameTransformer;
Expand Down Expand Up @@ -679,6 +676,19 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri
} catch (Exception e) {
return wrapInstantiationProblem(ctxt, e);
}

// [databind#1516]: Inject back references for managed reference creator properties
if (creator.hasManagedReferenceProperties()) {
for (SettableBeanProperty prop : creator.properties()) {
if (prop instanceof ManagedReferenceProperty managedProp) {
Object value = buffer.getParameter(ctxt, prop);
if (value != null) {
managedProp.set(ctxt, bean, value);
}
}
}
}

p.assignCurrentValue(bean);
// [databind#4938] Since 2.19, allow returning `null` from creator,
// but if so, need to skip all possibly relevant content
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import tools.jackson.databind.deser.SettableAnyProperty;
import tools.jackson.databind.deser.SettableBeanProperty;
import tools.jackson.databind.deser.ValueInstantiator;
import tools.jackson.databind.deser.impl.ManagedReferenceProperty;
import tools.jackson.databind.deser.impl.ObjectIdReader;
import tools.jackson.databind.util.NameTransformer;

Expand Down Expand Up @@ -54,6 +55,14 @@ public final class PropertyBasedCreator
*/
protected final BitSet _injectablePropIndexes;

/**
* Flag indicating whether any creator properties are ManagedReferenceProperty instances,
* used to optimize deserialization by skipping back-reference injection when not needed.
*
* @since 3.1
*/
protected final boolean _hasManagedReferenceProperties;

/*
/**********************************************************************
/* Construction, initialization
Expand Down Expand Up @@ -93,6 +102,7 @@ protected PropertyBasedCreator(DeserializationContext ctxt,
_propertyCount = len;
_propertiesInOrder = new SettableBeanProperty[len];
BitSet injectablePropIndexes = null;
boolean hasManagedRef = false;

for (int i = 0; i < len; ++i) {
SettableBeanProperty prop = creatorProps[i];
Expand All @@ -107,9 +117,16 @@ protected PropertyBasedCreator(DeserializationContext ctxt,
}
injectablePropIndexes.set(i);
}

// [databind#1516]: detect whether any ManagedReferenceProperty exists
// so we can avoid iterating over all properties when none are present
if (!hasManagedRef && (prop instanceof ManagedReferenceProperty)) {
hasManagedRef = true;
}
}

_injectablePropIndexes = injectablePropIndexes;
_hasManagedReferenceProperties = hasManagedRef;
}

protected PropertyBasedCreator(PropertyBasedCreator base,
Expand All @@ -121,6 +138,7 @@ protected PropertyBasedCreator(PropertyBasedCreator base,
_injectablePropIndexes = base._injectablePropIndexes;
_propertyLookup = propertyLookup;
_propertiesInOrder = allProperties;
_hasManagedReferenceProperties = base._hasManagedReferenceProperties;
}

/**
Expand Down Expand Up @@ -238,6 +256,13 @@ public SettableBeanProperty findCreatorProperty(int propertyIndex) {
return null;
}

/**
* @since 3.1
*/
public boolean hasManagedReferenceProperties() {
return _hasManagedReferenceProperties;
}

/*
/**********************************************************************
/* Building process
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,31 +76,52 @@ public final void set(DeserializationContext ctxt, Object instance, Object value
@Override
public Object setAndReturn(DeserializationContext ctxt, Object instance, Object value)
{
_setBackReference(ctxt, instance, value);
// and then the forward reference itself
return delegate.setAndReturn(ctxt, instance, value);
}

/*
/**********************************************************************
/* Helper classes
/**********************************************************************
*/

/**
* Helper method to inject back reference into value(s).
*/
private void _setBackReference(DeserializationContext ctxt, Object instance, Object value)
{
if (value == null) {
return;
}
// 04-Feb-2014, tatu: As per [#390], it may be necessary to switch the
// ordering of forward/backward references, and start with back ref.
if (value != null) {
if (_isContainer) { // ok, this gets ugly... but has to do for now
if (value instanceof Object[]) {
for (Object ob : (Object[]) value) {
if (ob != null) { _backProperty.set(ctxt, ob, instance); }
if (_isContainer) { // ok, this gets ugly... but has to do for now
if (value instanceof Object[]) {
for (Object ob : (Object[]) value) {
if (ob != null) {
_backProperty.set(ctxt, ob, instance);
}
} else if (value instanceof Collection<?>) {
for (Object ob : (Collection<?>) value) {
if (ob != null) { _backProperty.set(ctxt, ob, instance); }
}
} else if (value instanceof Collection<?>) {
for (Object ob : (Collection<?>) value) {
if (ob != null) {
_backProperty.set(ctxt, ob, instance);
}
} else if (value instanceof Map<?,?>) {
for (Object ob : ((Map<?,?>) value).values()) {
if (ob != null) { _backProperty.set(ctxt, ob, instance); }
}
} else if (value instanceof Map<?,?>) {
for (Object ob : ((Map<?,?>) value).values()) {
if (ob != null) {
_backProperty.set(ctxt, ob, instance);
}
} else {
throw new IllegalStateException("Unsupported container type ("+value.getClass().getName()
+") when resolving reference '"+_referenceName+"'");
}
} else {
_backProperty.set(ctxt, value, instance);
throw new IllegalStateException("Unsupported container type (" + value.getClass().getName()
+ ") when resolving reference '" + _referenceName + "'");
}
} else {
_backProperty.set(ctxt, value, instance);
}
// and then the forward reference itself
return delegate.setAndReturn(ctxt, instance, value);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package tools.jackson.databind.tofix;
package tools.jackson.databind.struct;

import java.beans.ConstructorProperties;

Expand All @@ -9,7 +9,6 @@

import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.testutil.DatabindTestUtil;
import tools.jackson.databind.testutil.failure.JacksonTestFailureExpected;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
Expand Down Expand Up @@ -75,7 +74,6 @@ public ChildObject2(String id, String name,
" 'child': { 'id': 'def', 'name':'Bert' }\n" +
"}");

@JacksonTestFailureExpected
@Test
void withParentCreator() throws Exception {
ParentWithCreator result = MAPPER.readValue(PARENT_CHILD_JSON,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package tools.jackson.databind.struct;

import java.util.ArrayList;
import java.util.List;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import com.fasterxml.jackson.annotation.JsonProperty;

import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.testutil.DatabindTestUtil;

import static org.junit.jupiter.api.Assertions.*;

// [databind#1516]
public class KotlinGithub129Test extends DatabindTestUtil
{
private final ObjectMapper MAPPER = newJsonMapper();

static class Car {
private final long id;

@JsonManagedReference
private final List<Color> colors;

@JsonCreator
public Car(@JsonProperty("id") long id,
@JsonProperty("colors") List<Color> colors) {
this.id = id;
this.colors = colors != null ? colors : new ArrayList<>();
}

public long getId() { return id; }
public List<Color> getColors() { return colors; }
}

static class Color {
private final long id;
private final String code;

@JsonBackReference
private Car car;

@JsonCreator
public Color(@JsonProperty("id") long id,
@JsonProperty("code") String code) {
this.id = id;
this.code = code;
}

public long getId() { return id; }
public String getCode() { return code; }
public Car getCar() { return car; }
public void setCar(Car car) { this.car = car; }
}

@Test
public void testManagedReferenceOnCreator() throws Exception
{
Car car = new Car(100, new ArrayList<>());
Color color = new Color(100, "#FFFFF");
color.setCar(car);
car.getColors().add(color);

String json = MAPPER.writeValueAsString(car);
Car result = MAPPER.readValue(json, Car.class);

assertNotNull(result);
assertEquals(100, result.getId());
assertNotNull(result.getColors());
assertEquals(1, result.getColors().size());

Color resultColor = result.getColors().get(0);
assertEquals(100, resultColor.getId());
assertEquals("#FFFFF", resultColor.getCode());

assertNotNull(resultColor.getCar());
assertSame(result, resultColor.getCar());
}
}