Skip to content

Commit f6f5b92

Browse files
authored
Fix issues wrt #1381 (#5392)
1 parent c05c071 commit f6f5b92

File tree

9 files changed

+164
-134
lines changed

9 files changed

+164
-134
lines changed

src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import java.util.*;
77
import java.util.concurrent.atomic.AtomicReference;
88

9-
import com.fasterxml.jackson.annotation.JacksonInject;
109
import com.fasterxml.jackson.annotation.JsonFormat;
1110
import com.fasterxml.jackson.annotation.ObjectIdGenerator;
1211
import com.fasterxml.jackson.annotation.ObjectIdResolver;
@@ -468,20 +467,11 @@ public final Object findInjectableValue(Object valueId,
468467
BeanProperty forProperty, Object beanInstance, Boolean optional, Boolean useInput)
469468
throws JsonMappingException
470469
{
471-
if (_injectableValues == null) {
472-
// `useInput` and `optional` come from property annotation (if any);
473-
// they have precedence over global setting.
474-
if (Boolean.TRUE.equals(useInput)
475-
|| Boolean.TRUE.equals(optional)
476-
|| ((useInput == null || optional == null)
477-
&& !isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_INJECT_VALUE))) {
478-
return JacksonInject.Value.empty();
479-
}
480-
throw missingInjectableValueException(String.format(
481-
"No 'injectableValues' configured, cannot inject value with id '%s'", valueId),
482-
valueId, forProperty, beanInstance);
470+
InjectableValues injectables = _injectableValues;
471+
if (injectables == null) {
472+
injectables = InjectableValues.empty();
483473
}
484-
return _injectableValues.findInjectableValue(this, valueId, forProperty, beanInstance,
474+
return injectables.findInjectableValue(this, valueId, forProperty, beanInstance,
485475
optional, useInput);
486476
}
487477

src/main/java/com/fasterxml/jackson/databind/InjectableValues.java

Lines changed: 110 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@
1010
*/
1111
public abstract class InjectableValues
1212
{
13+
/**
14+
* @since 2.21
15+
*/
16+
public static InjectableValues empty() {
17+
return InjectableValues.Empty.INSTANCE;
18+
}
19+
1320
/**
1421
* Method called to find value identified by id <code>valueId</code> to
1522
* inject as value of specified property during deserialization, passing
@@ -46,25 +53,120 @@ public abstract Object findInjectableValue(Object valueId, DeserializationContex
4653
throws JsonMappingException;
4754

4855
/*
49-
/**********************************************************
50-
/* Standard implementation
51-
/**********************************************************
56+
/**********************************************************************
57+
/* Standard implementations
58+
/**********************************************************************
59+
*/
60+
61+
/**
62+
* Shared intermediate base class for standard implementations.
63+
*
64+
* @since 2.21
65+
*/
66+
public abstract static class Base
67+
extends InjectableValues
68+
implements java.io.Serializable
69+
{
70+
private static final long serialVersionUID = 1L;
71+
72+
protected String _validateKey(DeserializationContext ctxt, Object valueId,
73+
BeanProperty forProperty, Object beanInstance)
74+
throws JsonMappingException
75+
{
76+
if (!(valueId instanceof String)) {
77+
throw ctxt.missingInjectableValueException(
78+
String.format(
79+
"Unsupported injectable value id type (%s), expecting String",
80+
ClassUtil.classNameOf(valueId)),
81+
valueId, forProperty, beanInstance);
82+
}
83+
return (String) valueId;
84+
}
85+
86+
protected Object _handleMissingValue(DeserializationContext ctxt, String key,
87+
BeanProperty forProperty, Object beanInstance,
88+
Boolean optionalConfig, Boolean useInputConfig)
89+
throws JsonMappingException
90+
{
91+
// Different defaulting fo "optional" (default to FALSE) and
92+
// "useInput" (default to TRUE)
93+
94+
final boolean optional = Boolean.TRUE.equals(optionalConfig);
95+
final boolean useInput = Boolean.TRUE.equals(useInputConfig);
96+
97+
// [databind#1381]: 14-Nov-2025, tatu: This is a mess: (1) and (2) make sense
98+
// but (3) is debatable. However, for backward compatibility this is what
99+
// passes tests we have.
100+
101+
// Missing ok if:
102+
//
103+
// 1. `optional` is TRUE
104+
// 2. FAIL_ON_UNKNOWN_INJECT_VALUE is disabled
105+
// 3. `useInput` is TRUE and injection is NOT via constructor (implied
106+
// by beanInstance being non-null)
107+
if (optional
108+
|| !ctxt.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_INJECT_VALUE)
109+
|| (useInput && beanInstance != null)
110+
) {
111+
return null;
112+
}
113+
throw ctxt.missingInjectableValueException(
114+
String.format("No injectable value with id '%s' found (for property '%s')",
115+
key, forProperty.getName()),
116+
key, forProperty, beanInstance);
117+
}
118+
119+
/**
120+
* @deprecated in 2.20
121+
*/
122+
@Override
123+
@Deprecated // since 2.20
124+
public Object findInjectableValue(Object valueId, DeserializationContext ctxt,
125+
BeanProperty forProperty, Object beanInstance)
126+
throws JsonMappingException
127+
{
128+
return this.findInjectableValue(ctxt, valueId, forProperty, beanInstance,
129+
null, null);
130+
}
131+
}
132+
133+
/**
134+
* @since 2.21
52135
*/
136+
private static final class Empty
137+
extends Base
138+
implements java.io.Serializable
139+
{
140+
private static final long serialVersionUID = 1L;
141+
142+
final static Empty INSTANCE = new Empty();
143+
144+
@Override
145+
public Object findInjectableValue(DeserializationContext ctxt, Object valueId,
146+
BeanProperty forProperty, Object beanInstance,
147+
Boolean optional, Boolean useInput)
148+
throws JsonMappingException
149+
{
150+
final String key = _validateKey(ctxt, valueId, forProperty, beanInstance);
151+
return _handleMissingValue(ctxt, key, forProperty, beanInstance,
152+
optional, useInput);
153+
}
154+
}
53155

54156
/**
55157
* Simple standard implementation which uses a simple Map to
56158
* store values to inject, identified by simple String keys.
57159
*/
58160
public static class Std
59-
extends InjectableValues
161+
extends Base
60162
implements java.io.Serializable
61163
{
62164
private static final long serialVersionUID = 1L;
63165

64166
protected final Map<String,Object> _values;
65167

66168
public Std() {
67-
this(new HashMap<String,Object>());
169+
this(new HashMap<>());
68170
}
69171

70172
public Std(Map<String,Object> values) {
@@ -81,48 +183,19 @@ public Std addValue(Class<?> classKey, Object value) {
81183
return this;
82184
}
83185

84-
/**
85-
* @since 2.20
86-
*/
87186
@Override
88187
public Object findInjectableValue(DeserializationContext ctxt, Object valueId,
89188
BeanProperty forProperty, Object beanInstance,
90189
Boolean optional, Boolean useInput)
91190
throws JsonMappingException
92191
{
93-
if (!(valueId instanceof String)) {
94-
throw ctxt.missingInjectableValueException(
95-
String.format(
96-
"Unsupported injectable value id type (%s), expecting String",
97-
ClassUtil.classNameOf(valueId)),
98-
valueId, forProperty, beanInstance);
99-
}
100-
String key = (String) valueId;
192+
String key = _validateKey(ctxt, valueId, forProperty, beanInstance);
101193
Object ob = _values.get(key);
102194
if (ob == null && !_values.containsKey(key)) {
103-
if (Boolean.FALSE.equals(optional)
104-
|| ((optional == null)
105-
&& ctxt.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_INJECT_VALUE))) {
106-
throw ctxt.missingInjectableValueException(
107-
String.format("No injectable value with id '%s' found (for property '%s')",
108-
key, forProperty.getName()),
109-
valueId, forProperty, beanInstance);
110-
}
195+
return _handleMissingValue(ctxt, key, forProperty, beanInstance,
196+
optional, useInput);
111197
}
112198
return ob;
113199
}
114-
115-
/**
116-
* @deprecated in 2.20
117-
*/
118-
@Override
119-
@Deprecated // since 2.20
120-
public Object findInjectableValue(Object valueId, DeserializationContext ctxt,
121-
BeanProperty forProperty, Object beanInstance)
122-
throws JsonMappingException
123-
{
124-
return this.findInjectableValue(ctxt, valueId, forProperty, beanInstance,
125-
null, null);
126-
}
127200
}
128201
}

src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyValueBuffer.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -291,12 +291,12 @@ protected Object _findMissing(SettableBeanProperty prop) throws JsonMappingExcep
291291
}
292292

293293
// First: do we have injectable value?
294-
Object injectableValueId = prop.getInjectableValueId();
295-
if (injectableValueId != null) {
294+
final JacksonInject.Value injection = prop.getInjectionDefinition();
295+
if (injection != null) {
296296
// 10-Nov-2025: [databind#1381] Is this needed?
297297
_injectablePropIndexes.clear(prop.getCreatorIndex());
298298
return _context.findInjectableValue(prop.getInjectableValueId(),
299-
prop, null, null, null);
299+
prop, null, injection.getOptional(), injection.getUseInput());
300300
}
301301
// Second: required?
302302
if (prop.isRequired()) {
@@ -345,7 +345,7 @@ private void _inject(final SettableBeanProperty prop) throws JsonMappingExceptio
345345
final Object value = _context.findInjectableValue(injection.getId(),
346346
prop, prop.getMember(), injection.getOptional(), useInput);
347347

348-
if (value != JacksonInject.Value.empty()) {
348+
if (value != null) {
349349
int ix = prop.getCreatorIndex();
350350
_creatorParameters[ix] = value;
351351
}

src/main/java/com/fasterxml/jackson/databind/deser/impl/ValueInjector.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import java.io.IOException;
44

5-
import com.fasterxml.jackson.annotation.JacksonInject;
65
import com.fasterxml.jackson.databind.*;
76
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
87

@@ -70,7 +69,7 @@ public void inject(DeserializationContext context, Object beanInstance)
7069
{
7170
final Object value = findValue(context, beanInstance);
7271

73-
if (value == JacksonInject.Value.empty()) {
72+
if (value == null) {
7473
if (Boolean.FALSE.equals(_optional)) {
7574
throw context.missingInjectableValueException(
7675
String.format("No injectable value with id '%s' found (for property '%s')",

src/test/java/com/fasterxml/jackson/databind/deser/inject/JacksonInject1381DeserializationFeatureDisabledTest.java

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
package com.fasterxml.jackson.databind.deser.inject;
22

3-
import com.fasterxml.jackson.annotation.JacksonInject;
4-
import com.fasterxml.jackson.annotation.JsonCreator;
5-
import com.fasterxml.jackson.annotation.JsonProperty;
6-
import com.fasterxml.jackson.annotation.OptBoolean;
3+
import org.junit.jupiter.api.DisplayName;
4+
import org.junit.jupiter.api.Test;
5+
6+
import com.fasterxml.jackson.annotation.*;
7+
78
import com.fasterxml.jackson.databind.DeserializationFeature;
89
import com.fasterxml.jackson.databind.InjectableValues;
910
import com.fasterxml.jackson.databind.ObjectMapper;
10-
import com.fasterxml.jackson.databind.exc.ValueInstantiationException;
1111
import com.fasterxml.jackson.databind.testutil.DatabindTestUtil;
12-
import org.junit.jupiter.api.DisplayName;
13-
import org.junit.jupiter.api.Test;
1412

1513
import static org.junit.jupiter.api.Assertions.assertEquals;
16-
import static org.junit.jupiter.api.Assertions.assertThrows;
14+
import static org.junit.jupiter.api.Assertions.assertNull;
1715

1816
class JacksonInject1381DeserializationFeatureDisabledTest extends DatabindTestUtil {
1917
static class InputDefault {
@@ -115,23 +113,16 @@ public String getField() {
115113
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_INJECT_VALUE)
116114
.build();
117115

116+
// If we are not to fail on unknown injectable values, no exception either
118117
@Test
119118
@DisplayName("FAIL_ON_UNKNOWN_INJECT_VALUE NO, input NO, injectable NO, useInput DEFAULT|TRUE|FALSE => exception")
120-
void test1() {
121-
assertThrows(ValueInstantiationException.class,
122-
() -> plainMapper.readValue(empty, InputDefault.class));
123-
assertThrows(ValueInstantiationException.class,
124-
() -> plainMapper.readValue(empty, InputDefaultConstructor.class));
125-
126-
assertThrows(ValueInstantiationException.class,
127-
() -> plainMapper.readValue(empty, InputTrue.class));
128-
assertThrows(ValueInstantiationException.class,
129-
() -> plainMapper.readValue(empty, InputTrueConstructor.class));
130-
131-
assertThrows(ValueInstantiationException.class,
132-
() -> plainMapper.readValue(empty, InputFalse.class));
133-
assertThrows(ValueInstantiationException.class,
134-
() -> plainMapper.readValue(empty, InputFalseConstructor.class));
119+
void test1() throws Exception {
120+
assertNull(plainMapper.readValue(empty, InputDefault.class).getField());
121+
assertNull(plainMapper.readValue(empty, InputDefaultConstructor.class).getField());
122+
assertNull(plainMapper.readValue(empty, InputTrue.class).getField());
123+
assertNull(plainMapper.readValue(empty, InputTrueConstructor.class).getField());
124+
assertNull(plainMapper.readValue(empty, InputFalse.class).getField());
125+
assertNull(plainMapper.readValue(empty, InputFalseConstructor.class).getField());
135126
}
136127

137128
@Test

src/test/java/com/fasterxml/jackson/databind/deser/inject/JacksonInject1381Test.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,8 @@
33
import org.junit.jupiter.api.DisplayName;
44
import org.junit.jupiter.api.Test;
55

6-
import com.fasterxml.jackson.annotation.JacksonInject;
7-
import com.fasterxml.jackson.annotation.JsonCreator;
8-
import com.fasterxml.jackson.annotation.JsonProperty;
9-
import com.fasterxml.jackson.annotation.OptBoolean;
10-
6+
import com.fasterxml.jackson.annotation.*;
7+
import com.fasterxml.jackson.databind.DeserializationFeature;
118
import com.fasterxml.jackson.databind.InjectableValues;
129
import com.fasterxml.jackson.databind.ObjectMapper;
1310
import com.fasterxml.jackson.databind.exc.MissingInjectableValueExcepion;
@@ -117,6 +114,7 @@ public String getField() {
117114

118115
private final ObjectMapper plainMapper = newJsonMapper();
119116
private final ObjectMapper injectedMapper = jsonMapperBuilder()
117+
.enable(DeserializationFeature.FAIL_ON_UNKNOWN_INJECT_VALUE)
120118
.injectableValues(new InjectableValues.Std().addValue("key", "injected"))
121119
.build();
122120

0 commit comments

Comments
 (0)