Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
2f1ccd5
feat: jackson-databind#3072 values injected with JacksonInject can be…
giulong May 1, 2025
4ad53a0
feat: jackson-databind#3072 adding DeserializationFeature.FAIL_ON_UNK…
giulong May 1, 2025
f69e5c3
Merge branch '2.x' into feature/databind-3072-optional-inject
giulong May 1, 2025
d3e7c26
Merge branch '2.x' into feature/databind-3072-optional-inject
cowtowncoder May 2, 2025
91af4a2
Merge branch '2.x' into feature/databind-3072-optional-inject
cowtowncoder May 2, 2025
7b318aa
stylistic change
cowtowncoder May 2, 2025
11edc9f
Fix logic of global/override handling
cowtowncoder May 2, 2025
9516422
More clean up
cowtowncoder May 2, 2025
602bfbb
Merge branch '2.x' into feature/databind-3072-optional-inject
cowtowncoder May 2, 2025
1e8b910
Fix test wrt fixed message
cowtowncoder May 2, 2025
774deb5
feat(#3072): moving JacksonInject annotation introspection during des…
giulong May 3, 2025
f95db10
Merge branch '2.x' into feature/databind-3072-optional-inject
cowtowncoder May 3, 2025
4d7f37e
Merge branch '2.x' into feature/databind-3072-optional-inject
cowtowncoder May 8, 2025
edcfb22
feat(#3072): overloading methods and deprecating old ones
giulong May 9, 2025
c0094be
Merge pull request #1 from giulong/feature/databind-3072-optional-inj…
giulong May 9, 2025
b9d6c92
Merge branch '2.x' into feature/databind-3072-optional-inject
giulong May 9, 2025
91b7ff2
chore(#3072): code cleaning
giulong May 9, 2025
2d8f9f7
Minor changes
cowtowncoder May 9, 2025
2e094e7
Add release notes
cowtowncoder May 9, 2025
59fdc76
Minor clean up
cowtowncoder May 9, 2025
378e630
Add a test, fix problem found wrt precedence/checking
cowtowncoder May 10, 2025
1fcd267
More fixes
cowtowncoder May 10, 2025
e88234a
...
cowtowncoder May 10, 2025
a7d7083
Last minor test add
cowtowncoder May 10, 2025
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
Expand Up @@ -6,6 +6,7 @@
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.ObjectIdGenerator;
import com.fasterxml.jackson.annotation.ObjectIdResolver;
Expand Down Expand Up @@ -38,6 +39,8 @@
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.util.*;

import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_INJECT_VALUE;

/**
* Context for the process of deserialization a single root-level value.
* Used to allow passing in configuration settings and reusable temporary
Expand Down Expand Up @@ -463,15 +466,32 @@ public final boolean hasSomeOfFeatures(int featureMask) {
*/
public final JsonParser getParser() { return _parser; }

/**
* @since 2.20
*/
public final Object findInjectableValue(Object valueId,
BeanProperty forProperty, Object beanInstance)
BeanProperty forProperty, Object beanInstance, Boolean optional)
throws JsonMappingException
{
if (_injectableValues == null) {
if (Boolean.TRUE.equals(optional) || !isEnabled(FAIL_ON_UNKNOWN_INJECT_VALUE)) {
return JacksonInject.Value.empty();
}
return reportBadDefinition(ClassUtil.classOf(valueId), String.format(
"No 'injectableValues' configured, cannot inject value with id [%s]", valueId));
}
return _injectableValues.findInjectableValue(valueId, this, forProperty, beanInstance);
return _injectableValues.findInjectableValue(valueId, this, forProperty, beanInstance, optional);
}

/**
* @deprecated in 2.20
*/
@Deprecated // since 2.20
public final Object findInjectableValue(Object valueId,
BeanProperty forProperty, Object beanInstance)
throws JsonMappingException
{
return this.findInjectableValue(valueId, forProperty, beanInstance, null);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.fasterxml.jackson.databind;

import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.databind.cfg.ConfigFeature;
import com.fasterxml.jackson.databind.exc.InvalidNullException;

Expand Down Expand Up @@ -266,7 +267,7 @@ public enum DeserializationFeature implements ConfigFeature
* white space or comments, if supported by data format).
*<p>
* Feature is disabled by default (so that no check is made for possible trailing
* token(s)) for backwards compatibility reasons.
* token(s)) for backwards-compatibility reasons.
*
* @since 2.9
*/
Expand Down Expand Up @@ -336,6 +337,20 @@ public enum DeserializationFeature implements ConfigFeature
*/
FAIL_ON_UNEXPECTED_VIEW_PROPERTIES(false),

/**
* Feature that determines the handling of injected properties during deserialization.
*<p>
* When enabled, if an injected property is not encountered during deserialization,
* an exception is thrown.
* When disabled, no exception is thrown.
* @see JacksonInject#optional() to configure the same behavior on single properties,
*<p>
* In Jackson 2.x, this feature is enabled by default to maintain backwards-compatibility.
*
* @since 2.20
*/
FAIL_ON_UNKNOWN_INJECT_VALUE(true),

/*
/******************************************************
/* Structural conversion features
Expand Down
34 changes: 31 additions & 3 deletions src/main/java/com/fasterxml/jackson/databind/InjectableValues.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import com.fasterxml.jackson.databind.util.ClassUtil;

import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_INJECT_VALUE;

/**
* Abstract class that defines API for objects that provide value to
* "inject" during deserialization. An instance of this object
Expand All @@ -23,7 +25,16 @@ public abstract class InjectableValues
* @param forProperty Bean property in which value is to be injected
* @param beanInstance Bean instance that contains property to inject,
* if available; null if bean has not yet been constructed.
* @param optional Flag used for configuring the behavior when the value
* to inject is not found
*/
public abstract Object findInjectableValue(Object valueId, DeserializationContext ctxt,
BeanProperty forProperty, Object beanInstance, Boolean optional) throws JsonMappingException;

/**
* @deprecated in 2.20
*/
@Deprecated // since 2.20
public abstract Object findInjectableValue(Object valueId, DeserializationContext ctxt,
BeanProperty forProperty, Object beanInstance) throws JsonMappingException;

Expand Down Expand Up @@ -63,9 +74,12 @@ public Std addValue(Class<?> classKey, Object value) {
return this;
}

/**
* @since 2.20
*/
@Override
public Object findInjectableValue(Object valueId, DeserializationContext ctxt,
BeanProperty forProperty, Object beanInstance) throws JsonMappingException
BeanProperty forProperty, Object beanInstance, Boolean optional) throws JsonMappingException
{
if (!(valueId instanceof String)) {
ctxt.reportBadDefinition(ClassUtil.classOf(valueId),
Expand All @@ -75,10 +89,24 @@ public Object findInjectableValue(Object valueId, DeserializationContext ctxt,
}
String key = (String) valueId;
Object ob = _values.get(key);
if (ob == null && !_values.containsKey(key)) {
throw new IllegalArgumentException("No injectable value with id '"+key+"' found (for property '"+forProperty.getName()+"')");
if (ob == null && !_values.containsKey(key)
&& !Boolean.TRUE.equals(optional)
&& ctxt.isEnabled(FAIL_ON_UNKNOWN_INJECT_VALUE)) {
throw new IllegalArgumentException("No injectable value with id '" + key + "' " +
"found (for property '" + forProperty.getName() + "')");
}
return ob;
}

/**
* @deprecated in 2.20
*/
@Override
@Deprecated // since 2.20
public Object findInjectableValue(Object valueId, DeserializationContext ctxt,
BeanProperty forProperty, Object beanInstance) throws JsonMappingException
{
return this.findInjectableValue(valueId, ctxt, forProperty, beanInstance, null);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -239,9 +239,12 @@ public void addBackReferenceProperty(String referenceName, SettableBeanProperty
*/
}

/**
* @since 2.20
*/
public void addInjectable(PropertyName propName, JavaType propType,
Annotations contextAnnotations, AnnotatedMember member,
Object valueId)
Object valueId, Boolean optional)
throws JsonMappingException
{
if (_injectables == null) {
Expand All @@ -254,7 +257,19 @@ public void addInjectable(PropertyName propName, JavaType propType,
_handleBadAccess(e);
}
}
_injectables.add(new ValueInjector(propName, propType, member, valueId));
_injectables.add(new ValueInjector(propName, propType, member, valueId, optional));
}

/**
* @deprecated in 2.20
*/
@Deprecated // since 2.20
public void addInjectable(PropertyName propName, JavaType propType,
Annotations contextAnnotations, AnnotatedMember member,
Object valueId)
throws JsonMappingException
{
this.addInjectable(propName, propType, contextAnnotations, member, valueId, null);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -812,11 +812,16 @@ protected void addInjectables(DeserializationContext ctxt,
{
Map<Object, AnnotatedMember> raw = beanDesc.findInjectables();
if (raw != null) {
final AnnotationIntrospector introspector = ctxt.getAnnotationIntrospector();

for (Map.Entry<Object, AnnotatedMember> entry : raw.entrySet()) {
AnnotatedMember m = entry.getValue();
final JacksonInject.Value injectableValue = introspector.findInjectableValue(m);
final Boolean optional = injectableValue == null ? null : injectableValue.getOptional();

builder.addInjectable(PropertyName.construct(m.getName()),
m.getType(),
beanDesc.getClassAnnotations(), m, entry.getKey());
beanDesc.getClassAnnotations(), m, entry.getKey(), optional);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public CreatorProperty(PropertyName name, JavaType type, PropertyName wrapperNam
{
this(name, type, wrapperName, typeDeser, contextAnnotations, param, index,
(injectableValueId == null) ? null
: JacksonInject.Value.construct(injectableValueId, null),
: JacksonInject.Value.construct(injectableValueId, null, null),
metadata);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ protected Object _findMissing(SettableBeanProperty prop) throws JsonMappingExcep
Object injectableValueId = prop.getInjectableValueId();
if (injectableValueId != null) {
return _context.findInjectableValue(prop.getInjectableValueId(),
prop, null);
prop, null, null);
}
// Second: required?
if (prop.isRequired()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.io.IOException;

import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;

Expand All @@ -21,11 +22,30 @@ public class ValueInjector
*/
protected final Object _valueId;

/**
* Flag used for configuring the behavior when the value to inject is not found
*/
protected final Boolean _optional;

/**
* @since 2.20
*/
public ValueInjector(PropertyName propName, JavaType type,
AnnotatedMember mutator, Object valueId)
AnnotatedMember mutator, Object valueId, Boolean optional)
{
super(propName, type, null, mutator, PropertyMetadata.STD_OPTIONAL);
_valueId = valueId;
_optional = optional;
}

/**
* @deprecated in 2.20 (remove from 3.0)
*/
@Deprecated // since 2.20
public ValueInjector(PropertyName propName, JavaType type,
AnnotatedMember mutator, Object valueId)
{
this(propName, type, mutator, valueId, null);
}

/**
Expand All @@ -42,12 +62,15 @@ public ValueInjector(PropertyName propName, JavaType type,
public Object findValue(DeserializationContext context, Object beanInstance)
throws JsonMappingException
{
return context.findInjectableValue(_valueId, this, beanInstance);
return context.findInjectableValue(_valueId, this, beanInstance, _optional);
}

public void inject(DeserializationContext context, Object beanInstance)
throws IOException
{
_member.setValue(beanInstance, findValue(context, beanInstance));
final Object value = findValue(context, beanInstance);
if (!JacksonInject.Value.empty().equals(value)) {
Copy link
Member

Choose a reason for hiding this comment

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

Not quite sure why this test is needed... ?

_member.setValue(beanInstance, value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,7 @@ private Object _createUsingDelegate(AnnotatedWithParams delegateCreator,
if (prop == null) { // delegate
args[i] = delegate;
} else { // nope, injectable:
args[i] = ctxt.findInjectableValue(prop.getInjectableValueId(), prop, null);
args[i] = ctxt.findInjectableValue(prop.getInjectableValueId(), prop, null, null);
}
}
// and then try calling with full set of arguments
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -697,14 +697,19 @@ public JacksonInject.Value findInjectableValue(AnnotatedMember m) {

static class TestInjector extends InjectableValues {
@Override
public Object findInjectableValue(Object valueId, DeserializationContext ctxt, BeanProperty forProperty, Object beanInstance) {
public Object findInjectableValue(Object valueId, DeserializationContext ctxt, BeanProperty forProperty, Object beanInstance, Boolean optional) {
if (valueId == "jjj") {
UnreadableBean bean = new UnreadableBean();
bean.setValue(1);
return bean;
}
return null;
}

@Override
public Object findInjectableValue(Object valueId, DeserializationContext ctxt, BeanProperty forProperty, Object beanInstance) {
return this.findInjectableValue(valueId, ctxt, forProperty, beanInstance, null);
}
}

enum SimpleEnum { ONE, TWO }
Expand Down
Loading