-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Conversation
} | ||
p.nextToken(); | ||
return deserializeEnumUsingPropertyBased(p, ctxt, _propCreator); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Summary here,
- Simply flipped the
p.isExpectedStartObjectToken()
conditional (line 135) - Allow pass-through of case for possible "delegating-creator" (line 144) -- in other words, "fail if f value cannot possibly be delegating-creator"
@@ -132,18 +132,21 @@ 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 (_propCreator == null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I know this is existing code but this is wrong (not sure how I added such code :) ), wrt multi-threaded access.
Could you extract this into separate, synchronized method? At least it'd be slightly less bad.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure! no problem
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
synchronize assignment to _propCreator
right, @cowtowncoder ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, so it's transient
but that won't do anything for access. I think what'd make sense is using AtomicReference
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be thread-safe, now?
@@ -132,18 +135,21 @@ 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()) { | |||
_propCreatorRef.compareAndSet(null, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But wouldn't this create (and try set) a new instance every time? :-D
(instead of trying to get()
and only create-and-set if null
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this create (and try set) a new instance every time? :-D
hmm, Not really my intention 😅
Could you check again? Take below as an example. Invoking get()
and set
separately wouldn't be atomic anymore 🤔.
public void testAtomicRef() {
Bean expected = new Bean(0);
AtomicReference<Bean> reference = new AtomicReference<>(null);
reference.compareAndSet(null, expected);
reference.compareAndSet(null, new Bean(1));
reference.compareAndSet(null, new Bean(2));
// Passes
assertEquals(0,reference.get().a);
}
According to compareAndSet
documentation,
Atomically sets the value to newValue if the current value == expectedValue, with memory effects
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or we can do good-ol' double-checked locking like ones done in EnumDeserializer
below.
jackson-databind/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumDeserializer.java
Lines 432 to 445 in 68ba588
protected CompactStringObjectMap _getToStringLookup(DeserializationContext ctxt) { | |
CompactStringObjectMap lookup = _lookupByToString; | |
if (lookup == null) { | |
synchronized (this) { | |
lookup = _lookupByToString; | |
if (lookup == null) { | |
lookup = EnumResolver.constructUsingToString(ctxt.getConfig(), _enumClass()) | |
.constructLookup(); | |
_lookupByToString = lookup; | |
} | |
} | |
} | |
return lookup; | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, my problem is that instead of only creating it if needed, it creates new instance every time and sets -- and never uses value. You are correct in that get + set is not atomic, but I am not worried about atomicity here; if instance gets created 2 or 3 times that's fine. They are all identical. Goal is just to avoid always creating instance eagerly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And no need to do double-locking with synchronized: just do get()
from reference; if (and only if) null
returned, construct instance, set()
(or compareAndSet()
against null
). Use the props gotten or created.
Or leave as-is and I can add that as well, not a big deal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, my problem is that instead of only creating it if needed, it creates new instance every time and sets -- and never uses value.
Oh, I think I understand now! Thank you for the explanation! 🙏🏼🙏🏼.
The code creates newly instance in compareAndSet(null, PropertyBasedCreator.construct())
. The new intance will be ignored, but created "everytime" when _propCreator
is not null anymore.
PropertyBasedCreator.construct(ctxt, _valueInstantiator, _creatorProps, | ||
ctxt.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES))); | ||
|
||
if (_propCreatorRef.get() == null) { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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.
Merged: thank you once again @JooHyukKim ! |
@cowtowncoder always fun to help~ 🙏🏼🙏🏼 |
resolves #3566