Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/2.16' into 3877-requireTypeIdF…
Browse files Browse the repository at this point in the history
…orSubtype
  • Loading branch information
JooHyukKim committed May 4, 2023
2 parents ba36af7 + 23603ea commit 3df1268
Show file tree
Hide file tree
Showing 15 changed files with 504 additions and 21 deletions.
12 changes: 7 additions & 5 deletions release-notes/CREDITS-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -1583,16 +1583,18 @@ Hervé Boutemy (hboutemy@github)
(2.15.0)
Sim Yih Tsern (yihtsern@github)
* Contributed fix for #2974: Null coercion with `@JsonSetter` does not work with `java.lang.Record`
* Contributed fix for #2974: Null coercion with `@JsonSetter` does not work with `java.lang.Record`
(2.15.0)
* Contributed fix for #2992: Properties naming strategy do not work with Record
* Contributed fix for #2992: Properties naming strategy do not work with Record
(2.15.0)
* Contributed fix for #3180: Support `@JsonCreator` annotation on record classes
* Contributed fix for #3180: Support `@JsonCreator` annotation on record classes
(2.15.0)
* Contributed fix for #3297: `@JsonDeserialize(converter = ...)` does not work with Records
* Contributed fix for #3297: `@JsonDeserialize(converter = ...)` does not work with Records
(2.15.0)
* Contributed fix for #3342: `JsonTypeInfo.As.EXTERNAL_PROPERTY` does not work with record wrappers
* Contributed fix for #3342: `JsonTypeInfo.As.EXTERNAL_PROPERTY` does not work with record wrappers
(2.15.0)
* Contributed fix for #3894: Only avoid Records fields detection for deserialization
(2.15.1)
Ajay Siwach (Siwach16@github)
* Contributed #3637: Add enum features into `@JsonFormat.Feature`
Expand Down
9 changes: 9 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ Project: jackson-databind

No changes since 2.15

2.15.1 (not yet released)

#3894: Only avoid Records fields detection for deserialization
(contributed by Sim Y-T)
#3913: Issue with deserialization when there are unexpected properties (due
to null `StreamReadConstraints`)
(reported by @sbertault)
#3914: Fix TypeId serialization for `JsonTypeInfo.Id.DEDUCTION`, native type ids

2.15.0 (23-Apr-2023)

#2536: Add `EnumFeature.READ_ENUM_KEYS_USING_INDEX` to work with
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri

// polymorphic?
if (bean.getClass() != _beanType.getRawClass()) {
return handlePolymorphic(p, ctxt, bean, unknown);
return handlePolymorphic(p, ctxt, p.streamReadConstraints(), bean, unknown);
}
if (unknown != null) { // nope, just extra unknown stuff...
bean = handleUnknownProperties(ctxt, bean, unknown);
Expand Down Expand Up @@ -538,7 +538,7 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri
if (unknown != null) {
// polymorphic?
if (bean.getClass() != _beanType.getRawClass()) { // lgtm [java/dereferenced-value-may-be-null]
return handlePolymorphic(null, ctxt, bean, unknown);
return handlePolymorphic(null, ctxt, p.streamReadConstraints(), bean, unknown);
}
// no, just some extra unknown properties
return handleUnknownProperties(ctxt, bean, unknown);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1735,18 +1735,41 @@ protected void handleIgnoredProperty(JsonParser p, DeserializationContext ctxt,
* @param p (optional) If not null, parser that has more properties to handle
* (in addition to buffered properties); if null, all properties are passed
* in buffer
* @deprecated use {@link #handlePolymorphic(JsonParser, DeserializationContext, StreamReadConstraints, Object, TokenBuffer)}
*/
@Deprecated
protected Object handlePolymorphic(JsonParser p, DeserializationContext ctxt,
Object bean, TokenBuffer unknownTokens)
throws IOException
{
final StreamReadConstraints streamReadConstraints = p == null ?
StreamReadConstraints.defaults() : p.streamReadConstraints();
return handlePolymorphic(p, ctxt, streamReadConstraints, bean, unknownTokens);
}

/**
* Method called in cases where we may have polymorphic deserialization
* case: that is, type of Creator-constructed bean is not the type
* of deserializer itself. It should be a sub-class or implementation
* class; either way, we may have more specific deserializer to use
* for handling it.
*
* @param p (optional) If not null, parser that has more properties to handle
* (in addition to buffered properties); if null, all properties are passed
* in buffer
* @since 2.15.1
*/
protected Object handlePolymorphic(JsonParser p, DeserializationContext ctxt,
StreamReadConstraints streamReadConstraints, Object bean, TokenBuffer unknownTokens)
throws IOException
{
// First things first: maybe there is a more specific deserializer available?
JsonDeserializer<Object> subDeser = _findSubclassDeserializer(ctxt, bean, unknownTokens);
if (subDeser != null) {
if (unknownTokens != null) {
// need to add END_OBJECT marker first
unknownTokens.writeEndObject();
JsonParser p2 = unknownTokens.asParser(p.streamReadConstraints());
JsonParser p2 = unknownTokens.asParser(streamReadConstraints);
p2.nextToken(); // to get to first data field
bean = subDeser.deserialize(p2, ctxt, bean);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p,
}
// polymorphic?
if (builder.getClass() != _beanType.getRawClass()) {
return handlePolymorphic(p, ctxt, builder, unknown);
return handlePolymorphic(p, ctxt, p.streamReadConstraints(), builder, unknown);
}
if (unknown != null) { // nope, just extra unknown stuff...
builder = handleUnknownProperties(ctxt, builder, unknown);
Expand Down Expand Up @@ -440,7 +440,7 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p,
if (unknown != null) {
// polymorphic?
if (builder.getClass() != _beanType.getRawClass()) {
return handlePolymorphic(null, ctxt, builder, unknown);
return handlePolymorphic(null, ctxt, p.streamReadConstraints(), builder, unknown);
}
// no, just some extra unknown properties
return handleUnknownProperties(ctxt, builder, unknown);
Expand Down Expand Up @@ -669,7 +669,7 @@ protected Object deserializeUsingPropertyBasedWithUnwrapped(JsonParser p,
continue; // never gets here
}
if (builder.getClass() != _beanType.getRawClass()) {
return handlePolymorphic(p, ctxt, builder, tokens);
return handlePolymorphic(p, ctxt, p.streamReadConstraints(), builder, tokens);
}
return deserializeWithUnwrapped(p, ctxt, builder, tokens);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,8 @@ protected void collectAll()

// 15-Jan-2023, tatu: [databind#3736] Let's avoid detecting fields of Records
// altogether (unless we find a good reason to detect them)
if (!isRecordType()) {
// 17-Apr-2023: Need Records' fields for serialization for cases like [databind#3895] & [databind#3628]
if (!isRecordType() || _forSerialization) {
_addFields(props); // note: populates _fieldRenameMappings
}
_addMethods(props);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.BeanProperty;

Expand Down Expand Up @@ -39,9 +40,19 @@ public WritableTypeId writeTypePrefix(JsonGenerator g,
// write surrounding Object or Array start/end markers. But
// we are not to generate type id to write (compared to base class)

if (idMetadata.valueShape.isStructStart()
// also: do not try to write native type id
&& !g.canWriteTypeId()) {
if (idMetadata.valueShape.isStructStart()) {
// 03-May-2023, tatu: [databind#3914]: should not write Native Type Id;
// but may need to write the value start marker
if (g.canWriteTypeId()) {
idMetadata.wrapperWritten = false;
if (idMetadata.valueShape == JsonToken.START_OBJECT) {
g.writeStartObject(idMetadata.forValue);
} else if (idMetadata.valueShape == JsonToken.START_ARRAY) {
g.writeStartArray(idMetadata.forValue);
}
return idMetadata;
}
// But for non-wrapper types can just use the default handling
return g.writeTypePrefix(idMetadata);
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@
* as well as some other commonly
* needed aspects (addition of custom {@link AbstractTypeResolver}s,
* {@link com.fasterxml.jackson.databind.deser.ValueInstantiator}s).
* <p>
* NOTE: that [de]serializers are registered as "default" [de]serializers.
* As a result, they will have lower priority than the ones indicated through annotations on
* both Class and property-associated annotations -- for example,
* {@link com.fasterxml.jackson.databind.annotation.JsonDeserialize}.<br/>
* In cases where both module-based [de]serializers and annotation-based [de]serializers are registered,
* the [de]serializer specified by the annotation will take precedence.
*<p>
* NOTE: although it is not expected that sub-types should need to
* override {@link #setupModule(SetupContext)} method, if they choose
Expand Down Expand Up @@ -312,6 +319,8 @@ protected SimpleModule setNamingStrategy(PropertyNamingStrategy naming) {
*<p>
* WARNING! "Last one wins" rule is applied.
* Possible earlier addition of a serializer for a given Class will be replaced.
* <p>
* NOTE: This method registers "default" (de)serializers only. See a note on precedence in class JavaDoc.
*/
public SimpleModule addSerializer(JsonSerializer<?> ser)
{
Expand All @@ -325,13 +334,17 @@ public SimpleModule addSerializer(JsonSerializer<?> ser)

/**
* Method for adding serializer to handle values of specific type.
* <p>
* NOTE: This method registers "default" (de)serializers only. See a note on precedence in class JavaDoc.
*<p>
* WARNING! Type matching only uses type-erased {@code Class} and should NOT
* be used when registering serializers for generic types like
* {@link java.util.Collection} and {@link java.util.Map}.
*<p>
* WARNING! "Last one wins" rule is applied.
* Possible earlier addition of a serializer for a given Class will be replaced.
* <p>
* NOTE: This method registers "default" (de)serializers only. See a note on precedence in class JavaDoc.
*/
public <T> SimpleModule addSerializer(Class<? extends T> type, JsonSerializer<T> ser)
{
Expand All @@ -344,6 +357,9 @@ public <T> SimpleModule addSerializer(Class<? extends T> type, JsonSerializer<T>
return this;
}

/**
* NOTE: This method registers "default" (de)serializers only. See a note on precedence in class JavaDoc.
*/
public <T> SimpleModule addKeySerializer(Class<? extends T> type, JsonSerializer<T> ser)
{
_checkNotNull(type, "type to register key serializer for");
Expand All @@ -370,6 +386,8 @@ public <T> SimpleModule addKeySerializer(Class<? extends T> type, JsonSerializer
*<p>
* WARNING! "Last one wins" rule is applied.
* Possible earlier addition of a serializer for a given Class will be replaced.
* <p>
* NOTE: This method registers "default" (de)serializers only. See a note on precedence in class JavaDoc.
*/
public <T> SimpleModule addDeserializer(Class<T> type, JsonDeserializer<? extends T> deser)
{
Expand All @@ -382,6 +400,9 @@ public <T> SimpleModule addDeserializer(Class<T> type, JsonDeserializer<? extend
return this;
}

/**
* NOTE: This method registers "default" (de)serializers only. See a note on precedence in class JavaDoc.
*/
public SimpleModule addKeyDeserializer(Class<?> type, KeyDeserializer deser)
{
_checkNotNull(type, "type to register key deserializer for");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.fasterxml.jackson.databind.failing;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.BaseMapTest;

// [databinding#3897] This is failing test for `Record` class deserialization with single field annotated with
// `JsonProperty.Access.WRITE_ONLY`. Regression from Jackson 2.14.2
public class RecordDeserialization3897Test extends BaseMapTest {

record Example(
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
String value
) {}

// Passes in 2.14.2, but does not in 2.15.0
public void testRecordWithWriteOnly() throws Exception {
final String JSON = a2q("{'value':'foo'}");

Example result = newJsonMapper().readValue(JSON, Example.class);

assertEquals("foo", result.value());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.fasterxml.jackson.databind.records;

import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.BaseMapTest;
import com.fasterxml.jackson.databind.ObjectMapper;

public class RecordIgnoreNonAccessorGetterTest extends BaseMapTest {

// [databind#3628]
interface InterfaceWithGetter {

String getId();

String getName();
}

@JsonPropertyOrder({"id", "name", "count"}) // easier to assert when JSON field ordering is always the same
record RecordWithInterfaceWithGetter(String name) implements InterfaceWithGetter {

@Override
public String getId() {
return "ID:" + name;
}

@Override
public String getName() {
return name;
}

// [databind#3895]
public int getCount() {
return 999;
}
}

private final ObjectMapper MAPPER = newJsonMapper();

public void testSerializeIgnoreInterfaceGetter_WithoutUsingVisibilityConfig() throws Exception {
String json = MAPPER.writeValueAsString(new RecordWithInterfaceWithGetter("Bob"));

assertEquals("{\"id\":\"ID:Bob\",\"name\":\"Bob\",\"count\":999}", json);
}

public void testSerializeIgnoreInterfaceGetter_UsingVisibilityConfig() throws Exception {
MAPPER.setVisibility(PropertyAccessor.GETTER, Visibility.NONE);
MAPPER.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);

String json = MAPPER.writeValueAsString(new RecordWithInterfaceWithGetter("Bob"));

assertEquals("{\"name\":\"Bob\"}", json);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ static class Pojo3847 {
}

public record PlainRecord(String fieldName) {}
public record IntRecord(String description, int value) {}

public record FixedRecord(@JsonProperty("field_name") String fieldName) {}

Expand Down Expand Up @@ -101,4 +102,15 @@ public void testRecordFixerNullHandlingEmptyJson() throws Exception {
verifyException(e, "Invalid `null` value encountered for property \"field_name\"");
}
}

public void testRecordDefaultNullDeserialization() throws Exception {
PlainRecord pr = new ObjectMapper().readValue("{}", PlainRecord.class);
assertNull(pr.fieldName);
}

public void testIntRecordDefaultNullDeserialization() throws Exception {
IntRecord ir = new ObjectMapper().readValue("{}", IntRecord.class);
assertNull(ir.description);
assertEquals(0, ir.value);
}
}
Loading

0 comments on commit 3df1268

Please sign in to comment.