UnwrappedPropertyHandler.java
package tools.jackson.databind.deser.impl;
import java.util.*;
import tools.jackson.core.*;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.PropertyName;
import tools.jackson.databind.ValueDeserializer;
import tools.jackson.databind.deser.SettableBeanProperty;
import tools.jackson.databind.deser.bean.BeanDeserializerBase;
import tools.jackson.databind.deser.bean.PropertyValueBuffer;
import tools.jackson.databind.util.NameTransformer;
import tools.jackson.databind.util.TokenBuffer;
/**
* Object that is responsible for handling acrobatics related to
* deserializing "unwrapped" values; sets of properties that are
* embedded (inlined) as properties of parent JSON object.
*/
public class UnwrappedPropertyHandler
{
/**
* @since 2.19
*/
public static final String JSON_UNWRAPPED_NAME_PREFIX = "@JsonUnwrapped/";
/**
* @since 2.19
*/
protected final List<SettableBeanProperty> _creatorProperties;
protected final List<SettableBeanProperty> _properties;
/**
* Set of all nested property names from unwrapped deserializers.
*/
protected final Set<String> _nestedPropertyNames;
/**
* Flag indicating whether any unwrapped deserializer has an AnySetter,
* which means it can handle any property name.
*/
protected final boolean _hasNestedAnySetter;
public UnwrappedPropertyHandler() {
_creatorProperties = new ArrayList<>();
_properties = new ArrayList<>();
// placeholder: won't be modified in-place
_nestedPropertyNames = Collections.emptySet();
_hasNestedAnySetter = false;
}
protected UnwrappedPropertyHandler(List<SettableBeanProperty> creatorProps,
List<SettableBeanProperty> props,
Set<String> nestedPropertyNames,
boolean hasNestedAnySetter) {
_creatorProperties = creatorProps;
_properties = props;
_nestedPropertyNames = nestedPropertyNames;
_hasNestedAnySetter = hasNestedAnySetter;
}
/**
* Creates a new UnwrappedPropertyHandler with initialized nested property names cache.
*
* @since 3.1
*/
public UnwrappedPropertyHandler initializedNestedPropertyNames() {
Set<String> nestedNames = new HashSet<>();
boolean hasAnySetter = _collectNestedPropertyNames(_properties, _creatorProperties, nestedNames);
return new UnwrappedPropertyHandler(_creatorProperties, _properties, nestedNames, hasAnySetter);
}
/**
* @since 2.19
*/
public void addCreatorProperty(SettableBeanProperty property) {
_creatorProperties.add(property);
}
public void addProperty(SettableBeanProperty property) {
_properties.add(property);
}
public UnwrappedPropertyHandler renameAll(DeserializationContext ctxt,
NameTransformer transformer)
{
List<SettableBeanProperty> renamedCreatorProps = renameProperties(ctxt, _creatorProperties, transformer);
List<SettableBeanProperty> renamedProps = renameProperties(ctxt, _properties, transformer);
// Collect nested property names and check for AnySetter
Set<String> nestedNames = new HashSet<>();
boolean hasAnySetter = _collectNestedPropertyNames(renamedProps, renamedCreatorProps, nestedNames);
return new UnwrappedPropertyHandler(renamedCreatorProps, renamedProps, nestedNames, hasAnySetter);
}
private List<SettableBeanProperty> renameProperties(DeserializationContext ctxt,
Collection<SettableBeanProperty> properties,
NameTransformer transformer
) {
List<SettableBeanProperty> newProps = new ArrayList<>(properties.size());
for (SettableBeanProperty prop : properties) {
if (prop == null) {
newProps.add(null);
continue;
}
newProps.add(prop.unwrapped(ctxt, transformer));
}
return newProps;
}
/**
* @since 2.19
*/
public PropertyValueBuffer processUnwrappedCreatorProperties(JsonParser originalParser,
DeserializationContext ctxt, PropertyValueBuffer values, TokenBuffer buffered)
{
for (SettableBeanProperty prop : _creatorProperties) {
JsonParser p = buffered.asParserOnFirstToken(ctxt);
values.assignParameter(prop, prop.deserialize(p, ctxt));
}
return values;
}
@SuppressWarnings("resource")
public Object processUnwrapped(JsonParser originalParser, DeserializationContext ctxt,
Object bean, TokenBuffer buffered)
{
for (SettableBeanProperty prop : _properties) {
JsonParser p = buffered.asParserOnFirstToken(ctxt);
prop.deserializeAndSet(p, ctxt, bean);
}
return bean;
}
/**
* Generates a placeholder name for creator properties that don't have a name,
* but are marked with `@JsonWrapped` annotation.
*
* @since 2.19
*/
public static PropertyName creatorParamName(int index) {
return new PropertyName(JSON_UNWRAPPED_NAME_PREFIX + index);
}
/**
* Method that checks if the given property name belongs to any unwrapped property.
*
* @return {@code true} if any nested deserializers has an "any-setter".
*
* @since 3.1
*/
public boolean hasUnwrappedProperty(String propName) {
// If any nested deserializer has AnySetter, it can handle any property
if (_hasNestedAnySetter) {
return true;
}
return _nestedPropertyNames.contains(propName);
}
/**
* Collects all nested property names from unwrapped deserializers.
*
* @since 3.1
*/
public void collectNestedPropertyNamesTo(Set<String> names) {
_collectNestedPropertyNames(_properties, _creatorProperties, names);
}
/**
* Helper method to collect nested property names.
*
* @return {@code true} if any property deserializer has AnySetter.
*
* @since 3.1
*/
private boolean _collectNestedPropertyNames(List<SettableBeanProperty> properties,
List<SettableBeanProperty> creatorProperties,
Set<String> names) {
boolean hasAnySetter = false;
for (SettableBeanProperty prop : properties) {
if (_collectDeserializerPropertyNames(prop, names)) {
hasAnySetter = true;
}
}
for (SettableBeanProperty prop : creatorProperties) {
if (_collectDeserializerPropertyNames(prop, names)) {
hasAnySetter = true;
}
}
return hasAnySetter;
}
/**
* Helper method to collect property names from a property's deserializer.
*
* @return {@code true} if the property deserializer has AnySetter.
*/
private boolean _collectDeserializerPropertyNames(SettableBeanProperty prop, Set<String> names) {
if (prop != null) {
ValueDeserializer<?> deser = prop.getValueDeserializer();
if (deser instanceof BeanDeserializerBase bd) {
// Recursively collect property names
bd.collectAllPropertyNamesTo(names);
return bd.hasAnySetter();
}
}
return false;
}
}