Skip to content

Add FunctionalScalarDeserializer for functional-style deserialization#5595

Merged
cowtowncoder merged 9 commits intoFasterXML:3.xfrom
jhan0121:4004-functional-deserializer
Jan 22, 2026
Merged

Add FunctionalScalarDeserializer for functional-style deserialization#5595
cowtowncoder merged 9 commits intoFasterXML:3.xfrom
jhan0121:4004-functional-deserializer

Conversation

@jhan0121
Copy link
Copy Markdown
Contributor

Implements: #4004

This PR add functional-style deserializer extending StdScalarDeserializer that allows using Function<String, T> or BiFunction<JsonParser, DeserializationContext, T> for simple scalar-to-object conversions.

…FasterXML#4004)

Add functional-style deserializer extending StdScalarDeserializer that
allows using Function<String, T> or BiFunction<JsonParser, DeserializationContext, T>
for simple scalar-to-object conversions.
@cowtowncoder cowtowncoder added the cla-needed PR looks good (although may also require code review), but CLA needed from submitter label Jan 18, 2026
@github-actions
Copy link
Copy Markdown

🧪 Code Coverage Report

Metric Coverage Change
Instructions coverage 79.59% 📉 -0.450%
Branches branches 72.87% 📉 -0.200%

Coverage data generated from JaCoCo test results

@cowtowncoder cowtowncoder added cla-received PR already covered by CLA (optional label) and removed cla-needed PR looks good (although may also require code review), but CLA needed from submitter labels Jan 18, 2026
@github-actions
Copy link
Copy Markdown

🧪 Code Coverage Report

Metric Coverage Change
Instructions coverage 80.01% 📉 -0.030%
Branches branches 73.03% 📉 -0.040%

Coverage data generated from JaCoCo test results

@github-actions
Copy link
Copy Markdown

🧪 Code Coverage Report

Metric Coverage Change
Instructions coverage 80.02% 📉 -0.020%
Branches branches 73.04% 📉 -0.030%

Coverage data generated from JaCoCo test results

* Usage examples:
* <pre>
* // Simple case - method reference
* new FunctionalScalarDeserializer&lt;&gt;(Bar.class, Bar::of)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's fix the brackets, and ones below.

Suggested change
* new FunctionalScalarDeserializer&lt;&gt;(Bar.class, Bar::of)
* new FunctionalScalarDeserializer<>(Bar.class, Bar::of)

/**
* Tests for {@link FunctionalScalarDeserializer}.
*/
// [databind#4004]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's add more meaningful information, see this test class

* Tests for {@link FunctionalScalarDeserializer}.
*/
// [databind#4004]
public class FunctionalScalarDeserializerTest
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
public class FunctionalScalarDeserializerTest
public class FunctionalScalarDeserializer4004Test

Bar result = mapper.readValue("\"\"", Bar.class);
assertNull(result);
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

EOF

* Subclasses may override to provide custom behavior.
* Default implementation returns null.
*/
protected Object _deserializeFromEmptyStringDefault(DeserializationContext ctxt)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does this need to be protected why not private?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point. I've reconsidered the design.

The core intent of FunctionalScalarDeserializer is to accept a function to deserialize into a user-specified type. Users are expected to customize behavior by passing a Function or BiFunction, rather than by subclassing.

Given this, I believe we should start with the simplest form. If subclassing becomes necessary in the future, we can easily expand visibility from private to protected without breaking backward compatibility.

I'll apply the following changes:

  • _deserializeFromEmptyStringDefault: change to private
  • _deserializeFromEmptyString: change to private
  • Remove the copy constructor (as it is currently unused)

@Test
public void testSimpleFunctionFromString() throws Exception
{
SimpleModule module = new SimpleModule("test", Version.unknownVersion());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since version value is fixed toVersion.unknownVersion() in all places, let's replace ALL SimpleModule declarations with

Suggested change
SimpleModule module = new SimpleModule("test", Version.unknownVersion());
SimpleModule module = new SimpleModule("test");

@JooHyukKim
Copy link
Copy Markdown
Member

JooHyukKim commented Jan 19, 2026

🧪 Code Coverage Report

Metric Coverage Change
Instructions coverage 80.02% 📉 -0.020%
Branches branches 73.04% 📉 -0.030%

Coverage data generated from JaCoCo test results

Also @jhan0121 could this PR result in non-negative test coverage change? :-) tnx

@jhan0121
Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review, @JooHyukKim! I have addressed your comments and updated the code accordingly.

@github-actions
Copy link
Copy Markdown

🧪 Code Coverage Report

Metric Coverage Change
Instructions coverage 80.05% 📈 +0.000%
Branches branches 73.06% 📈 +0.010%

Coverage data generated from JaCoCo test results

}

try {
return _function.apply(p, ctxt);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ok this is problematic: this ignores text altogether; and will fail for

text = ctxt.extractScalarFromObject(p, this, _valueClass);

case from above. I think it will be necessary to keep track of type of original Function being passed and:

  1. In case of "complex" 2-arg function, just invoke that function without doing about any of things in this method
  2. In case of "simple" from-string function, use code here and just pass text to function

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for catching this! You're right, the extracted text was being ignored.

I've fixed it by separating the storage into two distinct fields to handle each case correctly:

  • _biFunction: invoked directly without preprocessing.
  • _stringFunction: receives the extracted text directly.

@github-actions
Copy link
Copy Markdown

🧪 Code Coverage Report

Metric Coverage Change
Instructions coverage 80.06% 📈 +0.010%
Branches branches 73.07% 📈 +0.020%

Coverage data generated from JaCoCo test results

Copy link
Copy Markdown
Member

@cowtowncoder cowtowncoder left a comment

Choose a reason for hiding this comment

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

Looks good, almost ready to merge. Couple of minor comments added.

@github-actions
Copy link
Copy Markdown

🧪 Code Coverage Report

Metric Coverage Change
Instructions coverage 80.05% 📈 +0.000%
Branches branches 73.08% 📈 +0.030%

Coverage data generated from JaCoCo test results

@cowtowncoder cowtowncoder changed the title Implement #4004: Add FunctionalScalarDeserializer for functional-style deserialization Add FunctionalScalarDeserializer for functional-style deserialization Jan 22, 2026
@cowtowncoder cowtowncoder self-requested a review January 22, 2026 03:47
Copy link
Copy Markdown
Member

@cowtowncoder cowtowncoder left a comment

Choose a reason for hiding this comment

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

LGTM

@github-actions
Copy link
Copy Markdown

🧪 Code Coverage Report

Metric Coverage Change
Instructions coverage 80.05% 📈 +0.000%
Branches branches 73.07% 📈 +0.020%

Coverage data generated from JaCoCo test results

@cowtowncoder cowtowncoder merged commit 08df310 into FasterXML:3.x Jan 22, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-received PR already covered by CLA (optional label)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants