FasterXML / FasterXML/jackson-databind
`@JsonIgnoreProperties` bypassed for Creator properties with builders and external type ids
- Dominant language
- Java
- Stars
- 3.7k
- Forks
- 1.5k
- Avg merge
- 3d 6h
- Merged PRs (30d)
- 28
Description
### Describe the bug
`@JsonIgnoreProperties` is not applied to Creator properties on three of the property-based-Creator deserialization paths, so a name the annotation lists still gets deserialized and passed to the constructor or builder.
`BeanDeserializer` checks the ignore set before assigning a Creator property (the `[databind#4629]` guard, needed because a type with a property-based `@JsonCreator` resolves a valid `creatorProp` and so never reaches the by-name ignore check further down the loop). That guard is present in `deserializeUsingPropertyBased` and `deserializeUsingPropertyBasedWithUnwrapped`, but not in `deserializeUsingPropertyBasedWithExternalTypeId`. `BuilderBasedDeserializer` does not have it on any of its Creator loops (`_deserializeUsingPropertyBased`, `deserializeUsingPropertyBasedWithUnwrapped`).
So the ignore configuration works for a plain POJO or record, and stops working for the same type once it is built through a `@JsonPOJOBuilder`, or once it has an `@JsonTypeInfo(include = EXTERNAL_PROPERTY)` property.
### Version Information
3.2.2-SNAPSHOT (also reproduces on 3.3.0-SNAPSHOT). 3.1 has a narrower, record-only form of the guard, so its behavior differs.
### Reproduction
Builder case:
```java
@JsonDeserialize(builder = BuiltValue.Builder.class)
static class BuiltValue {
public final int id;
public final String secret;
BuiltValue(int id, String secret) { this.id = id; this.secret = secret; }
@JsonPOJOBuilder(withPrefix = "")
static class Builder {
private final int id;
private final String secret;
@JsonCreator
public Builder(@JsonProperty("id") int id, @JsonProperty("secret") String secret) {
this.id = id;
this.secret = secret;
}
public BuiltValue build() { return new BuiltValue(id, secret); }
}
}
static class BuilderWrapper {
@JsonIgnoreProperties("secret")
public BuiltValue child;
}
BuilderWrapper w = MAPPER.readValue(
"{\"child\":{\"id\":13,\"secret\":\"leaked\"}}", BuilderWrapper.class);
// w.child.secret is "leaked"; expected null
```
External type id case:
```java
static abstract class Animal { public String name; }
static class Dog extends Animal { }
static class ExtTypeValue {
public final String secret;
public final Animal value;
@JsonCreator
public ExtTypeValue(@JsonProperty("secret") String secret,
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type")
@JsonSubTypes({ @JsonSubTypes.Type(value = Dog.class, name = "dog") })
@JsonProperty("value") Animal value) {
this.secret = secret;
this.value = value;
}
}
static class ExtTypeWrapper {
@JsonIgnoreProperties("secret")
public ExtTypeValue child;
}
ExtTypeWrapper w = MAPPER.readValue(
"{\"child\":{\"secret\":\"leaked\",\"type\":\"dog\",\"value\":{\"name\":\"Rex\"}}}",
ExtTypeWrapper.class);
// w.child.secret is "leaked"; expected null
```
The same thing happens when the builder has `@JsonUnwrapped` properties, which routes through `BuilderBasedDeserializer.deserializeUsingPropertyBasedWithUnwrapped`.
### Expected behavior
`secret` should be left unset in all three cases, matching what the plain property-based Creator path already does.
### Additional context
The practical impact is that the annotation silently stops filtering input for these shapes, so a property an application deliberately excluded from binding is accepted from the incoming document. Happy to open a PR adding the same guard to the three loops.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with BeanDeserializer.deserializeUsingPropertyBasedWithExternalTypeId and the two BuilderBasedDeserializer Creator loops, comparing them with the existing guard in deserializeUsingPropertyBased and deserializeUsingPropertyBasedWithUnwrapped. Reproduce the builder, unwrapped, and external-type-id cases from the issue, then verify that ignored creator properties remain unset while the existing plain Creator behavior is unchanged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- backend-api-design
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 74/100