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 Enum deserialization with JsonFormat.Shape.OBJECT using both DELEGATING and PROPERTIES creator modes #3851

Merged
merged 6 commits into from
Apr 1, 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
@@ -1,6 +1,7 @@
package com.fasterxml.jackson.databind.deser.std;

import java.io.IOException;
import java.util.concurrent.atomic.AtomicReference;

import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParser;
Expand Down Expand Up @@ -40,10 +41,12 @@ class FactoryBasedEnumDeserializer

/**
* Lazily instantiated property-based creator.
* Introduced in 2.8 and wrapped with {@link AtomicReference} in 2.15
*
* @since 2.15
*
* @since 2.8
*/
private transient PropertyBasedCreator _propCreator;
private AtomicReference<PropertyBasedCreator> _propCreatorRef = new AtomicReference<>(null);

public FactoryBasedEnumDeserializer(Class<?> cls, AnnotatedMethod f, JavaType paramType,
ValueInstantiator valueInstantiator, SettableBeanProperty[] creatorProps)
Expand Down Expand Up @@ -132,18 +135,22 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx
// 30-Mar-2020, tatu: For properties-based one, MUST get JSON Object (before
// 2.11, was just assuming match)
if (_creatorProps != null) {
if (!p.isExpectedStartObjectToken()) {
if (p.isExpectedStartObjectToken()) {
if (_propCreatorRef.get() == null) {
Copy link
Member

@cowtowncoder cowtowncoder Apr 1, 2023

Choose a reason for hiding this comment

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

Ok, I'll change this afterwards.... realized that for serializability of deserializer, better to use volatile after all.
Thank you for changes

Copy link
Member Author

@JooHyukKim JooHyukKim Apr 1, 2023

Choose a reason for hiding this comment

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

@cowtowncoder I can change it to volatile and handle it like before if you'd like?

Copy link
Member

Choose a reason for hiding this comment

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

@JooHyukKim yes, basically I did that; no need to go back and forth. Sorry about confusion.

(the problem with JDK (de)serialization of JsonDeserializers is that we do not want to serialize fields like this, but in case of AtomicReference would need to make sure it's not-null ... volatile is easier, and performance won't matter a lot here-- although to be completely honest I think lazy construction here is probably not a good idea. Could just create eagerly.

Copy link
Member Author

@JooHyukKim JooHyukKim Apr 1, 2023

Choose a reason for hiding this comment

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

@JooHyukKim yes,

Sure, I will make a PR soon.·

Oh you did already.

Copy link
Member

Choose a reason for hiding this comment

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

Right, just the volatile part.

_propCreatorRef.compareAndSet(null,
PropertyBasedCreator.construct(ctxt, _valueInstantiator, _creatorProps,
ctxt.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)));
}
p.nextToken();
return deserializeEnumUsingPropertyBased(p, ctxt, _propCreatorRef.get());
}
// If value cannot possibly be delegating-creator,
if (!_valueInstantiator.canCreateFromString()) {
final JavaType targetType = getValueType(ctxt);
ctxt.reportInputMismatch(targetType,
"Input mismatch reading Enum %s: properties-based `@JsonCreator` (%s) expects JSON Object (JsonToken.START_OBJECT), got JsonToken.%s",
ClassUtil.getTypeDescription(targetType), _factory, p.currentToken());
}
if (_propCreator == null) {
_propCreator = PropertyBasedCreator.construct(ctxt, _valueInstantiator, _creatorProps,
ctxt.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES));
"Input mismatch reading Enum %s: properties-based `@JsonCreator` (%s) expects JSON Object (JsonToken.START_OBJECT), got JsonToken.%s",
ClassUtil.getTypeDescription(targetType), _factory, p.currentToken());
}
p.nextToken();
return deserializeEnumUsingPropertyBased(p, ctxt, _propCreator);
}
Copy link
Member Author

@JooHyukKim JooHyukKim Mar 29, 2023

Choose a reason for hiding this comment

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

Summary here,

  1. Simply flipped the p.isExpectedStartObjectToken() conditional (line 135)
  2. Allow pass-through of case for possible "delegating-creator" (line 144) -- in other words, "fail if f value cannot possibly be delegating-creator"


// 12-Oct-2021, tatu: We really should only get here if and when String
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.fasterxml.jackson.failing;
package com.fasterxml.jackson.databind.deser.creators;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
Expand Down Expand Up @@ -98,7 +98,8 @@ public static EnumB fromString(String type) {
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
enum EnumC {
A("AType"),
B("BType");
B("BType"),
C("CType");

private final String type;

Expand All @@ -121,6 +122,16 @@ public static EnumC create(@JsonProperty("type") String type) {
}
}

static class DelegatingCreatorEnumWrapper {
public EnumA enumA;
public EnumB enumB;
}

static class PropertiesCreatorEnumWrapper {
public EnumA enumA;
public EnumC enumC;
}

/*
/**********************************************************
/* Tests
Expand Down Expand Up @@ -179,4 +190,21 @@ public void testPojoCreatorModeDelegating() throws Exception {

assertEquals("properties", pojo1.name);
}

public void testDelegatingCreatorEnumWrapper() throws Exception {
DelegatingCreatorEnumWrapper wrapper = newJsonMapper()
.readValue(a2q("{'enumA':'AType', 'enumB': 'BType'}"), DelegatingCreatorEnumWrapper.class);

assertEquals(EnumA.A, wrapper.enumA);
assertEquals(EnumB.B, wrapper.enumB);
}

public void testPropertiesCreatorEnumWrapper() throws Exception {
PropertiesCreatorEnumWrapper wrapper = newJsonMapper()
.readValue(a2q("{'enumA':{'type':'AType'}, 'enumC': {'type':'CType'}}"), PropertiesCreatorEnumWrapper.class);

assertEquals(EnumA.A, wrapper.enumA);
assertEquals(EnumC.C, wrapper.enumC);
}

}