Skip to content

Commit abe0a50

Browse files
committed
Merge branch '3.0' into 3.x
2 parents f28318b + a2f3801 commit abe0a50

22 files changed

+1058
-79
lines changed

attic/ImmutableBitSet.java

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package com.fasterxml.jackson.databind.util;
2+
3+
import java.util.BitSet;
4+
5+
public class ImmutableBitSet extends BitSet
6+
{
7+
private static final long serialVersionUID = 1L;
8+
9+
private ImmutableBitSet(BitSet bits) {
10+
super();
11+
_parentOr(bits);
12+
}
13+
14+
public static ImmutableBitSet of(BitSet bits) {
15+
return new ImmutableBitSet(bits);
16+
}
17+
18+
private void _parentOr(BitSet set) {
19+
super.or(set);
20+
}
21+
22+
@Override
23+
public void and(BitSet set) {
24+
_failMutableOperation();
25+
}
26+
27+
@Override
28+
public void andNot(BitSet set) {
29+
_failMutableOperation();
30+
}
31+
32+
@Override
33+
public void or(BitSet set) {
34+
_failMutableOperation();
35+
}
36+
37+
@Override
38+
public void xor(BitSet set) {
39+
_failMutableOperation();
40+
}
41+
42+
@Override
43+
public void clear() {
44+
_failMutableOperation();
45+
}
46+
47+
@Override
48+
public void clear(int ix) {
49+
_failMutableOperation();
50+
}
51+
52+
@Override
53+
public void clear(int from, int to) {
54+
_failMutableOperation();
55+
}
56+
57+
@Override
58+
public void flip(int bitIndex) {
59+
_failMutableOperation();
60+
}
61+
62+
@Override
63+
public void flip(int from, int to) {
64+
_failMutableOperation();
65+
}
66+
67+
@Override
68+
public void set(int bitIndex) {
69+
_failMutableOperation();
70+
}
71+
72+
@Override
73+
public void set(int bitIndex, boolean state) {
74+
_failMutableOperation();
75+
}
76+
77+
@Override
78+
public void set(int from, int to) {
79+
_failMutableOperation();
80+
}
81+
82+
private void _failMutableOperation() {
83+
throw new UnsupportedOperationException("ImmutableBitSet does not support modification");
84+
}
85+
}

release-notes/CREDITS-2.x

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1966,6 +1966,8 @@ Giulio Longfils (@giulong)
19661966
* Contributed #4218: If `@JacksonInject` is specified for field and deserialized by
19671967
the Creator, the inject process will be executed twice
19681968
(2.20.0)
1969+
* Contributed #1381: Add a way to specify "inject-only" with `@JacksonInject`
1970+
(2.21.0)
19691971

19701972
Plamen Tanov (@ptanov)
19711973
* Reported #2678: `@JacksonInject` added to property overrides value from the JSON

release-notes/VERSION-2.x

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ Project: jackson-databind
66

77
2.21.0 (not yet released)
88

9+
#1381: Add a way to specify "inject-only" with `@JacksonInject`
10+
(fix by Giulio L)
911
#1547: Un-deprecate `SerializationFeature.WRITE_EMPTY_JSON_ARRAYS`
1012
#5045: If there is a no-parameter constructor marked as `JsonCreator` and
1113
a constructor reported as `DefaultCreator`, latter is incorrectly used

src/main/java/tools/jackson/databind/DeserializationContext.java

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import java.text.ParseException;
55
import java.util.*;
66

7-
import com.fasterxml.jackson.annotation.JacksonInject;
87
import com.fasterxml.jackson.annotation.JsonFormat;
98
import com.fasterxml.jackson.annotation.ObjectIdGenerator;
109
import com.fasterxml.jackson.annotation.ObjectIdResolver;
@@ -476,18 +475,11 @@ public final boolean isEnabled(StreamReadCapability cap) {
476475
public final Object findInjectableValue(Object valueId,
477476
BeanProperty forProperty, Object beanInstance, Boolean optional, Boolean useInput)
478477
{
479-
if (_injectableValues == null) {
480-
// `optional` comes from property annotation (if any); has precedence
481-
// over global setting.
482-
if (Boolean.TRUE.equals(optional)
483-
|| (optional == null && !isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_INJECT_VALUE))) {
484-
return JacksonInject.Value.empty();
485-
}
486-
throw missingInjectableValueException(String.format(
487-
"No 'injectableValues' configured, cannot inject value with id '%s'", valueId),
488-
valueId, forProperty, beanInstance);
478+
InjectableValues injectables = _injectableValues;
479+
if (injectables == null) {
480+
injectables = InjectableValues.empty();
489481
}
490-
return _injectableValues.findInjectableValue(this, valueId, forProperty, beanInstance,
482+
return injectables.findInjectableValue(this, valueId, forProperty, beanInstance,
491483
optional, useInput);
492484
}
493485

src/main/java/tools/jackson/databind/InjectableValues.java

Lines changed: 96 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
public abstract class InjectableValues
1414
implements Snapshottable<InjectableValues>
1515
{
16+
public static InjectableValues empty() {
17+
return InjectableValues.Empty.INSTANCE;
18+
}
19+
1620
/**
1721
* Method called to find value identified by id <code>valueId</code> to
1822
* inject as value of specified property during deserialization, passing
@@ -36,17 +40,99 @@ public abstract Object findInjectableValue(DeserializationContext ctxt,
3640
throws JacksonException;
3741

3842
/*
39-
/**********************************************************
40-
/* Standard implementation
41-
/**********************************************************
43+
/**********************************************************************
44+
/* Standard implementations
45+
/**********************************************************************
4246
*/
4347

48+
/**
49+
* Shared intermediate base class for standard implementations.
50+
*/
51+
public abstract static class Base
52+
extends InjectableValues
53+
implements java.io.Serializable
54+
{
55+
private static final long serialVersionUID = 1L;
56+
57+
protected String _validateKey(DeserializationContext ctxt, Object valueId,
58+
BeanProperty forProperty, Object beanInstance)
59+
throws JacksonException
60+
{
61+
if (!(valueId instanceof String)) {
62+
throw ctxt.missingInjectableValueException(
63+
String.format(
64+
"Unsupported injectable value id type (%s), expecting String",
65+
ClassUtil.classNameOf(valueId)),
66+
valueId, forProperty, beanInstance);
67+
}
68+
return (String) valueId;
69+
}
70+
71+
protected Object _handleMissingValue(DeserializationContext ctxt, String key,
72+
BeanProperty forProperty, Object beanInstance,
73+
Boolean optionalConfig, Boolean useInputConfig)
74+
throws JacksonException
75+
{
76+
// Different defaulting fo "optional" (default to FALSE) and
77+
// "useInput" (default to TRUE)
78+
79+
final boolean optional = Boolean.TRUE.equals(optionalConfig);
80+
final boolean useInput = Boolean.TRUE.equals(useInputConfig);
81+
82+
// [databind#1381]: 14-Nov-2025, tatu: This is a mess: (1) and (2) make sense
83+
// but (3) is debatable. However, for backward compatibility this is what
84+
// passes tests we have.
85+
86+
// Missing ok if:
87+
//
88+
// 1. `optional` is TRUE
89+
// 2. FAIL_ON_UNKNOWN_INJECT_VALUE is disabled
90+
// 3. `useInput` is TRUE and injection is NOT via constructor (implied
91+
// by beanInstance being non-null)
92+
if (optional
93+
|| !ctxt.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_INJECT_VALUE)
94+
|| (useInput && beanInstance != null)
95+
) {
96+
return null;
97+
}
98+
throw ctxt.missingInjectableValueException(
99+
String.format("No injectable value with id '%s' found (for property '%s')",
100+
key, forProperty.getName()),
101+
key, forProperty, beanInstance);
102+
}
103+
}
104+
105+
private static final class Empty
106+
extends Base
107+
implements java.io.Serializable
108+
{
109+
private static final long serialVersionUID = 1L;
110+
111+
final static Empty INSTANCE = new Empty();
112+
113+
@Override
114+
public Empty snapshot() {
115+
return this;
116+
}
117+
118+
@Override
119+
public Object findInjectableValue(DeserializationContext ctxt, Object valueId,
120+
BeanProperty forProperty, Object beanInstance,
121+
Boolean optional, Boolean useInput)
122+
throws JacksonException
123+
{
124+
final String key = _validateKey(ctxt, valueId, forProperty, beanInstance);
125+
return _handleMissingValue(ctxt, key, forProperty, beanInstance,
126+
optional, useInput);
127+
}
128+
}
129+
44130
/**
45131
* Simple standard implementation which uses a simple Map to
46132
* store values to inject, identified by simple String keys.
47133
*/
48134
public static class Std
49-
extends InjectableValues
135+
extends Base
50136
implements java.io.Serializable
51137
{
52138
private static final long serialVersionUID = 1L;
@@ -85,26 +171,13 @@ public Object findInjectableValue(DeserializationContext ctxt,
85171
BeanProperty forProperty, Object beanInstance,
86172
Boolean optional, Boolean useInput)
87173
{
88-
if (valueId instanceof String key) {
89-
Object ob = _values.get(key);
90-
if (ob == null && !_values.containsKey(key)) {
91-
if (Boolean.FALSE.equals(optional)
92-
|| ((optional == null)
93-
&& ctxt.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_INJECT_VALUE))) {
94-
throw ctxt.missingInjectableValueException(
95-
String.format("No injectable value with id '%s' found (for property '%s')",
96-
key, forProperty.getName()),
97-
valueId, forProperty, beanInstance);
98-
}
99-
}
100-
return ob;
101-
} else {
102-
throw ctxt.missingInjectableValueException(
103-
String.format(
104-
"Unsupported injectable value id type (%s), expecting String",
105-
ClassUtil.classNameOf(valueId)),
106-
valueId, forProperty, beanInstance);
174+
String key = _validateKey(ctxt, valueId, forProperty, beanInstance);
175+
Object ob = _values.get(key);
176+
if (ob == null && !_values.containsKey(key)) {
177+
return _handleMissingValue(ctxt, key, forProperty, beanInstance,
178+
optional, useInput);
107179
}
180+
return ob;
108181
}
109182
}
110183
}

src/main/java/tools/jackson/databind/deser/CreatorProperty.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,11 @@ public class CreatorProperty
3737
protected final AnnotatedParameter _annotated;
3838

3939
/**
40-
* Id of value to inject, if value injection should be used for this parameter
40+
* Injection settings, if value injection should be used for this parameter
4141
* (in addition to, or instead of, regular deserialization).
42+
*<p>
43+
* NOTE: badly named, should be more like "_injectionDefinition" but
44+
* renaming would be a breaking (internal) change.
4245
*/
4346
protected final JacksonInject.Value _injectableValue;
4447

@@ -261,6 +264,11 @@ public Object getInjectableValueId() {
261264
return (_injectableValue == null) ? null : _injectableValue.getId();
262265
}
263266

267+
@Override // since 2.21
268+
public JacksonInject.Value getInjectionDefinition() {
269+
return _injectableValue;
270+
}
271+
264272
@Override
265273
public boolean isInjectionOnly() {
266274
return (_injectableValue != null) && !_injectableValue.willUseInput(true);

src/main/java/tools/jackson/databind/deser/SettableBeanProperty.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package tools.jackson.databind.deser;
22

33
import java.lang.annotation.Annotation;
4+
45
import java.lang.reflect.InvocationTargetException;
56

7+
import com.fasterxml.jackson.annotation.JacksonInject;
8+
69
import tools.jackson.core.*;
710
import tools.jackson.core.util.InternCache;
811
import tools.jackson.databind.*;
@@ -469,6 +472,12 @@ public int getCreatorIndex() {
469472
*/
470473
public Object getInjectableValueId() { return null; }
471474

475+
/**
476+
* Accessor for injection definition, if this bean property supports
477+
* value injection.
478+
*/
479+
public JacksonInject.Value getInjectionDefinition() { return null; }
480+
472481
/**
473482
* Accessor for checking whether this property is injectable, and if so,
474483
* ONLY injectable (will not bind from input).

src/main/java/tools/jackson/databind/deser/bean/BeanAsArrayBuilderDeserializer.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,13 @@ protected final Object _deserializeUsingPropertyBased(final JsonParser p,
300300
continue;
301301
}
302302
if (creatorProp != null) {
303+
// [databind#1381]: if useInput=FALSE, skip deserialization from input
304+
if (creatorProp.isInjectionOnly()) {
305+
// Skip the input value, will be injected later in PropertyValueBuffer
306+
p.skipChildren();
307+
continue;
308+
}
309+
303310
// Last creator property to set?
304311
if (buffer.assignParameter(creatorProp, creatorProp.deserialize(p, ctxt))) {
305312
try {

src/main/java/tools/jackson/databind/deser/bean/BeanAsArrayDeserializer.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,13 @@ protected final Object _deserializeUsingPropertyBased(final JsonParser p, final
415415
continue;
416416
}
417417
if (creatorProp != null) {
418+
// [databind#1381]: if useInput=FALSE, skip deserialization from input
419+
if (creatorProp.isInjectionOnly()) {
420+
// Skip the input value, will be injected later in PropertyValueBuffer
421+
p.skipChildren();
422+
continue;
423+
}
424+
418425
// Last creator property to set?
419426
if (buffer.assignParameter(creatorProp, creatorProp.deserialize(p, ctxt))) {
420427
try {

0 commit comments

Comments
 (0)