Skip to content

Commit

Permalink
Implement FasterXML#1467: Support for @JsonCreator and @JsonUnwrapped
Browse files Browse the repository at this point in the history
  • Loading branch information
fxshlein committed Dec 20, 2023
1 parent bd9bf1b commit cb39812
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 162 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@
import com.fasterxml.jackson.databind.cfg.ConstructorDetector;
import com.fasterxml.jackson.databind.cfg.DeserializerFactoryConfig;
import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
import com.fasterxml.jackson.databind.deser.impl.CreatorCandidate;
import com.fasterxml.jackson.databind.deser.impl.CreatorCollector;
import com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators;
import com.fasterxml.jackson.databind.deser.impl.JavaUtilCollectionsDeserializers;
import com.fasterxml.jackson.databind.deser.impl.*;
import com.fasterxml.jackson.databind.deser.std.*;
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
import com.fasterxml.jackson.databind.ext.OptionalHandlerFactory;
Expand Down Expand Up @@ -52,12 +49,6 @@ public abstract class BasicDeserializerFactory
private final static Class<?> CLASS_MAP_ENTRY = Map.Entry.class;
private final static Class<?> CLASS_SERIALIZABLE = Serializable.class;

/**
* We need a placeholder for creator properties that don't have name
* but are marked with `@JsonWrapped` annotation.
*/
protected final static PropertyName UNWRAPPED_CREATOR_PARAM_NAME = new PropertyName("@JsonUnwrapped");

/*
/**********************************************************
/* Config
Expand Down Expand Up @@ -556,11 +547,8 @@ protected void _addImplicitConstructorCreators(DeserializationContext ctxt,
}
NameTransformer unwrapper = intr.findUnwrappingNameTransformer(param);
if (unwrapper != null) {
_reportUnwrappedCreatorProperty(ctxt, beanDesc, param);
/*
properties[i] = constructCreatorProperty(ctxt, beanDesc, UNWRAPPED_CREATOR_PARAM_NAME, i, param, null);
properties[i] = constructCreatorProperty(ctxt, beanDesc, UnwrappedPropertyHandler.UNWRAPPED_CREATOR_PARAM_NAME, i, param, null);
++explicitNameCount;
*/
continue;
}
// One more thing: implicit names are ok iff ctor has creator annotation
Expand Down Expand Up @@ -738,11 +726,8 @@ protected void _addImplicitFactoryCreators(DeserializationContext ctxt,
}
NameTransformer unwrapper = intr.findUnwrappingNameTransformer(param);
if (unwrapper != null) {
_reportUnwrappedCreatorProperty(ctxt, beanDesc, param);
/*
properties[i] = constructCreatorProperty(ctxt, beanDesc, UNWRAPPED_CREATOR_PARAM_NAME, i, param, null);
properties[i] = constructCreatorProperty(ctxt, beanDesc, UnwrappedPropertyHandler.UNWRAPPED_CREATOR_PARAM_NAME, i, param, null);
++implicitNameCount;
*/
continue;
}
// One more thing: implicit names are ok iff ctor has creator annotation
Expand Down Expand Up @@ -872,11 +857,8 @@ protected void _addExplicitPropertyCreator(DeserializationContext ctxt,
// as that will not work with Creators well at all
NameTransformer unwrapper = ctxt.getAnnotationIntrospector().findUnwrappingNameTransformer(param);
if (unwrapper != null) {
_reportUnwrappedCreatorProperty(ctxt, beanDesc, param);
/*
properties[i] = constructCreatorProperty(ctxt, beanDesc, UNWRAPPED_CREATOR_PARAM_NAME, i, param, null);
++explicitNameCount;
*/
properties[i] = constructCreatorProperty(ctxt, beanDesc, UnwrappedPropertyHandler.UNWRAPPED_CREATOR_PARAM_NAME, i, param, null);
continue;
}
name = candidate.findImplicitParamName(i);
_validateNamedPropertyParameter(ctxt, beanDesc, candidate, i,
Expand Down Expand Up @@ -1168,14 +1150,12 @@ protected void _validateNamedPropertyParameter(DeserializationContext ctxt,
}
}

// 01-Dec-2016, tatu: As per [databind#265] we cannot yet support passing
// of unwrapped values through creator properties, so fail fast
protected void _reportUnwrappedCreatorProperty(DeserializationContext ctxt,
BeanDescription beanDesc, AnnotatedParameter param)
throws JsonMappingException
protected void _reportUnnamedUnwrappedCreatorProperty(DeserializationContext ctxt,
BeanDescription beanDesc, AnnotatedParameter param)
throws JsonMappingException
{
ctxt.reportBadTypeDefinition(beanDesc,
"Cannot define Creator parameter %d as `@JsonUnwrapped`: combination not yet supported",
"Cannot define Creator parameter %d as `@JsonUnwrapped` without also specifying `@JsonProperty`",
param.getIndex());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -848,7 +848,7 @@ protected Object deserializeUsingPropertyBasedWithUnwrapped(JsonParser p, Deseri
if (buffer.readIdProperty(propName) && creatorProp == null) {
continue;
}
if (creatorProp != null) {
if (creatorProp != null && !_unwrappedPropertyHandler.isUnwrapped(creatorProp)) {
// Last creator property to set?
if (buffer.assignParameter(creatorProp,
_deserializeWithErrorWrapping(p, ctxt, creatorProp))) {
Expand Down Expand Up @@ -919,6 +919,10 @@ protected Object deserializeUsingPropertyBasedWithUnwrapped(JsonParser p, Deseri
}
}

// We could still have some unset creator properties that are unwrapped. These have to be processed last, because 'tokens' contains
// all the properties that remain after regular deserialization.
buffer = _unwrappedPropertyHandler.processUnwrappedCreatorProperties(p, ctxt, buffer, tokens);

// We hit END_OBJECT, so:
Object bean;
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,13 @@ public void resolve(DeserializationContext ctxt) throws JsonMappingException
if (unwrapped == null) {
unwrapped = new UnwrappedPropertyHandler();
}
unwrapped.addProperty(prop);

if (prop instanceof CreatorProperty) {
unwrapped.addCreatorProperty(prop);
} else {
unwrapped.addProperty(prop);
}

// 12-Dec-2014, tatu: As per [databind#647], we will have problems if
// the original property is left in place. So let's remove it now.
// 25-Mar-2017, tatu: Wonder if this could be problematic wrt creators?
Expand Down Expand Up @@ -1010,13 +1016,6 @@ protected NameTransformer _findPropertyUnwrapper(DeserializationContext ctxt,
if (am != null) {
NameTransformer unwrapper = ctxt.getAnnotationIntrospector().findUnwrappingNameTransformer(am);
if (unwrapper != null) {
// 01-Dec-2016, tatu: As per [databind#265] we cannot yet support passing
// of unwrapped values through creator properties, so fail fast
if (prop instanceof CreatorProperty) {
ctxt.reportBadDefinition(getValueType(), String.format(
"Cannot define Creator property \"%s\" as `@JsonUnwrapped`: combination not yet supported",
prop.getName()));
}
return unwrapper;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,62 +1,112 @@
package com.fasterxml.jackson.databind.deser.impl;

import java.io.IOException;
import java.util.*;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.PropertyName;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.util.NameTransformer;
import com.fasterxml.jackson.databind.util.TokenBuffer;

import java.io.IOException;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

/**
* 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
{
public class UnwrappedPropertyHandler {
/**
* We need a placeholder for creator properties that don't have name
* but are marked with `@JsonWrapped` annotation.
*/
public final static PropertyName UNWRAPPED_CREATOR_PARAM_NAME = new PropertyName("@JsonUnwrapped");

protected final List<SettableBeanProperty> _creatorProperties;
protected final List<SettableBeanProperty> _properties;
protected final Set<String> _unwrappedPropertyNames;

public UnwrappedPropertyHandler() {
_creatorProperties = new ArrayList<>();
_properties = new ArrayList<>();
_unwrappedPropertyNames = new HashSet<>();
}

public UnwrappedPropertyHandler() {
_properties = new ArrayList<SettableBeanProperty>();
}
protected UnwrappedPropertyHandler(List<SettableBeanProperty> props) {
protected UnwrappedPropertyHandler(List<SettableBeanProperty> creatorProps, List<SettableBeanProperty> props) {
_creatorProperties = creatorProps;
_properties = props;
_unwrappedPropertyNames = Stream.concat(creatorProps.stream(), props.stream())
.map(SettableBeanProperty::getName)
.collect(Collectors.toSet());
}

public void addCreatorProperty(SettableBeanProperty property) {
_creatorProperties.add(property);
_unwrappedPropertyNames.add(property.getName());
}

public void addProperty(SettableBeanProperty property) {
_properties.add(property);
_unwrappedPropertyNames.add(property.getName());
}

public UnwrappedPropertyHandler renameAll(NameTransformer transformer)
{
ArrayList<SettableBeanProperty> newProps = new ArrayList<SettableBeanProperty>(_properties.size());
for (SettableBeanProperty prop : _properties) {
public UnwrappedPropertyHandler renameAll(NameTransformer transformer) {
return new UnwrappedPropertyHandler(
renameProperties(_creatorProperties, transformer),
renameProperties(_properties, transformer)
);
}

private List<SettableBeanProperty> renameProperties(
Collection<SettableBeanProperty> properties,
NameTransformer transformer
) {
List<SettableBeanProperty> newProps = new ArrayList(properties.size());
for (SettableBeanProperty prop : properties) {
String newName = transformer.transform(prop.getName());
prop = prop.withSimpleName(newName);
JsonDeserializer<?> deser = prop.getValueDeserializer();
if (deser != null) {
@SuppressWarnings("unchecked")
JsonDeserializer<Object> newDeser = (JsonDeserializer<Object>)
deser.unwrappingDeserializer(transformer);
JsonDeserializer<Object> newDeser = (JsonDeserializer<Object>) deser.unwrappingDeserializer(transformer);
if (newDeser != deser) {
prop = prop.withValueDeserializer(newDeser);
}
}
newProps.add(prop);
}
return new UnwrappedPropertyHandler(newProps);
return newProps;
}

public boolean isUnwrapped(SettableBeanProperty property) {
return this._unwrappedPropertyNames.contains(property.getName());
}

public PropertyValueBuffer processUnwrappedCreatorProperties(
JsonParser originalParser,
DeserializationContext ctxt,
PropertyValueBuffer values,
TokenBuffer buffered
) throws IOException {
for (SettableBeanProperty prop : _creatorProperties) {
JsonParser p = buffered.asParser(originalParser.streamReadConstraints());
p.nextToken();
Object deserialized = prop.deserialize(p, ctxt);
values.assignParameter(prop, deserialized);
}

return values;
}

@SuppressWarnings("resource")
public Object processUnwrapped(JsonParser originalParser, DeserializationContext ctxt,
Object bean, TokenBuffer buffered)
throws IOException
{
for (int i = 0, len = _properties.size(); i < len; ++i) {
SettableBeanProperty prop = _properties.get(i);
Object bean, TokenBuffer buffered)
throws IOException {
for (SettableBeanProperty prop : _properties) {
JsonParser p = buffered.asParser(originalParser.streamReadConstraints());
p.nextToken();
prop.deserializeAndSet(p, ctxt, bean);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.deser.impl.UnwrappedPropertyHandler;
import com.fasterxml.jackson.databind.jdk14.JDK14Util;
import com.fasterxml.jackson.databind.util.ClassUtil;

Expand Down Expand Up @@ -716,6 +717,14 @@ private void _addCreatorParam(Map<String, POJOPropertyBuilder> props,
PropertyName pn = _annotationIntrospector.findNameForDeserialization(param);
boolean expl = (pn != null && !pn.isEmpty());
if (!expl) {
boolean unrwapping = _annotationIntrospector.findUnwrappingNameTransformer(param) != null;
if (unrwapping) {
POJOPropertyBuilder prop = _property(props, UnwrappedPropertyHandler.UNWRAPPED_CREATOR_PARAM_NAME);
prop.addCtor(param, UnwrappedPropertyHandler.UNWRAPPED_CREATOR_PARAM_NAME, false, true, false);
_creatorProperties.add(prop);
return;
}

if (impl.isEmpty()) {
// Important: if neither implicit nor explicit name, cannot make use of
// this creator parameter -- may or may not be a problem, verified at a later point.
Expand Down
Loading

0 comments on commit cb39812

Please sign in to comment.