Skip to content

Commit

Permalink
Fix handling of ObjectId-property in JsonIdentityInfo for uniform des…
Browse files Browse the repository at this point in the history
…erialization (#3868)
  • Loading branch information
JooHyukKim authored Jun 13, 2023
1 parent d8f17b9 commit 325d129
Show file tree
Hide file tree
Showing 2 changed files with 201 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,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) {
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;

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());
}
}
}

0 comments on commit 325d129

Please sign in to comment.