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

Fix handling of ObjectId-property in JsonIdentityInfo for uniform deserialization #3868

Merged
merged 17 commits into from
Jun 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,10 @@ public Object deserializeFromObject(JsonParser p, DeserializationContext ctxt) t
_handleTypedObjectId(p, ctxt, bean, id);
}
}
// [databind#3838]: since 2.16 Uniform handling of missing objectId
if (_objectIdReader != null && p.getCurrentToken() == JsonToken.END_OBJECT) {
cowtowncoder marked this conversation as resolved.
Show resolved Hide resolved
ctxt.reportUnresolvedObjectId(_objectIdReader, bean);
}
if (_injectables != null) {
injectValues(ctxt, bean);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package com.fasterxml.jackson.databind.deser;
cowtowncoder marked this conversation as resolved.
Show resolved Hide resolved

import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.BaseMapTest;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;

// [databind#3838]: Difference in the handling of ObjectId-property in JsonIdentityInfo depending on the deserialization route.
public class JsonIdentityInfoIdProperty3838Test extends BaseMapTest {

/*
/**********************************************************
/* Set Up
/**********************************************************
*/
interface ResultGetter {
String result();
}

@JsonIdentityInfo(property = "id", generator = ObjectIdGenerators.PropertyGenerator.class)
static class SetterBased implements ResultGetter {
private String id;

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

@Override
public String result() {
return id;
}
}

@JsonIdentityInfo(property = "id", generator = ObjectIdGenerators.PropertyGenerator.class)
static class CreatorBased implements ResultGetter {
private final String id;

@JsonCreator
CreatorBased(@JsonProperty(value = "id") String id) {
this.id = id;
}

public String getId() {
return id;
}

@Override
public String result() {
return id;
}
}

@JsonIdentityInfo(property = "id", generator = ObjectIdGenerators.PropertyGenerator.class)
static class DefaultConstructorBased implements ResultGetter {
public String id;

@Override
public String result() {
return id;
}
}

@JsonIdentityInfo(property = "id", generator = ObjectIdGenerators.PropertyGenerator.class)
static class StaticFactoryMethodBased implements ResultGetter {
private final String id;

private StaticFactoryMethodBased(String id) {
this.id = id;
}

public String getId() {
return id;
}

@JsonCreator
public static StaticFactoryMethodBased create(@JsonProperty("id") String id) {
return new StaticFactoryMethodBased(id);
}

@Override
public String result() {
return id;
}
}

@JsonIdentityInfo(property = "id", generator = ObjectIdGenerators.PropertyGenerator.class)
static class MultiArgConstructorBased implements ResultGetter {
private final String id;
private final int value;

@JsonCreator
MultiArgConstructorBased(@JsonProperty("id") String id, @JsonProperty("value") int value) {
this.id = id;
this.value = value;
}

public String getId() {
return id;
}

public int getValue() {
return value;
}

@Override
public String result() {
return id;
}
}

@JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id"
)
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.WRAPPER_OBJECT,
property = "type"
)
@JsonSubTypes({
@JsonSubTypes.Type(value = Concrete3838.class, name = "concrete_3838")
})
static class BaseType3838 implements ResultGetter {
public String id;

@Override
public String result() {
return id;
}
}

@JsonTypeName("concrete_3838")
static class Concrete3838 extends BaseType3838 {
public String location;

protected Concrete3838() {}

public Concrete3838(String id, String loc) {
this.id = id;
location = loc;
}
}

@JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "id")
static class IntSequencedBean implements ResultGetter{
public String value;

@Override
public String result() {
return value;
}
}

/*
/**********************************************************
/* Test
/**********************************************************
*/

private final ObjectMapper MAPPER = newJsonMapper();

final static Object[][] CLASS_AND_JSON_STRING = new Object[][]{
{SetterBased.class, "{'id':'great'}"},
{CreatorBased.class, "{'id':'great'}"},
{DefaultConstructorBased.class, "{'id':'great'}"},
{StaticFactoryMethodBased.class, "{'id':'great'}"},
{MultiArgConstructorBased.class, "{'id':'great','value':42}"},
{BaseType3838.class, "{'concrete_3838':{'id':'great','location':'Bangkok'}}"},
{IntSequencedBean.class, "{'id':-1,'value':'great'}"}
};

public void testUniformHandlingForMissingObjectId() throws Exception {
for (Object[] classAndJsonStrEntry : CLASS_AND_JSON_STRING) {
final Class<?> cls = (Class<?>) classAndJsonStrEntry[0];
final String jsonStr = (String) classAndJsonStrEntry[1];

// 1. throws MismatchedInputException with empty JSON object
try {
MAPPER.readValue("{}", cls);
fail("should not pass");
} catch (MismatchedInputException e) {
verifyException(e,
"No Object Id found for an instance of", "to",
"to assign to property 'id'");
}

// 2. but works with non-empty JSON object
ResultGetter nonEmptyObj = (ResultGetter) MAPPER.readValue(a2q(jsonStr), cls);
assertEquals("great", nonEmptyObj.result());
}
}
}