All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.fasterxml.jackson.databind.deser.SettableAnyProperty Maven / Gradle / Ivy

There is a newer version: 2.18.1
Show newest version
package com.fasterxml.jackson.databind.deser;

import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators;
import com.fasterxml.jackson.databind.deser.impl.ReadableObjectId.Referring;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.util.ClassUtil;

/**
 * Class that represents a "wildcard" set method which can be used
 * to generically set values of otherwise unmapped (aka "unknown")
 * properties read from JSON content.
 *

* Note: starting with 2.14, is {@code abstract} class with multiple * concrete implementations */ public abstract class SettableAnyProperty implements java.io.Serializable { private static final long serialVersionUID = 1L; /** * Method used for setting "any" properties, along with annotation * information. Retained to allow contextualization of any properties. */ protected final BeanProperty _property; /** * Annotated variant is needed for JDK serialization only */ protected final AnnotatedMember _setter; protected final boolean _setterIsField; protected final JavaType _type; protected JsonDeserializer _valueDeserializer; protected final TypeDeserializer _valueTypeDeserializer; /** * @since 2.9 */ protected final KeyDeserializer _keyDeserializer; /* /********************************************************** /* Life-cycle /********************************************************** */ public SettableAnyProperty(BeanProperty property, AnnotatedMember setter, JavaType type, KeyDeserializer keyDeser, JsonDeserializer valueDeser, TypeDeserializer typeDeser) { _property = property; _setter = setter; _type = type; _valueDeserializer = valueDeser; _valueTypeDeserializer = typeDeser; _keyDeserializer = keyDeser; _setterIsField = setter instanceof AnnotatedField; } /** * @since 2.14 */ public static SettableAnyProperty constructForMethod(DeserializationContext ctxt, BeanProperty property, AnnotatedMember field, JavaType valueType, KeyDeserializer keyDeser, JsonDeserializer valueDeser, TypeDeserializer typeDeser) { return new MethodAnyProperty(property, field, valueType, keyDeser, valueDeser, typeDeser); } /** * @since 2.14 */ public static SettableAnyProperty constructForMapField(DeserializationContext ctxt, BeanProperty property, AnnotatedMember field, JavaType valueType, KeyDeserializer keyDeser, JsonDeserializer valueDeser, TypeDeserializer typeDeser) { Class mapType = field.getRawType(); // 02-Aug-2022, tatu: Ideally would be resolved to a concrete type by caller but // alas doesn't appear to happen. Nor does `BasicDeserializerFactory` expose method // for finding default or explicit mappings. if (mapType == Map.class) { mapType = LinkedHashMap.class; } ValueInstantiator vi = JDKValueInstantiators.findStdValueInstantiator(ctxt.getConfig(), mapType); return new MapFieldAnyProperty(property, field, valueType, keyDeser, valueDeser, typeDeser, vi); } /** * @since 2.14 */ public static SettableAnyProperty constructForJsonNodeField(DeserializationContext ctxt, BeanProperty property, AnnotatedMember field, JavaType valueType, JsonDeserializer valueDeser) { return new JsonNodeFieldAnyProperty(property, field, valueType, valueDeser, ctxt.getNodeFactory()); } /** * @since 2.18 */ public static SettableAnyProperty constructForMapParameter(DeserializationContext ctxt, BeanProperty property, AnnotatedMember field, JavaType valueType, KeyDeserializer keyDeser, JsonDeserializer valueDeser, TypeDeserializer typeDeser, int parameterIndex ) { Class mapType = field.getRawType(); // 02-Aug-2022, tatu: Ideally would be resolved to a concrete type by caller but // alas doesn't appear to happen. Nor does `BasicDeserializerFactory` expose method // for finding default or explicit mappings. if (mapType == Map.class) { mapType = LinkedHashMap.class; } ValueInstantiator vi = JDKValueInstantiators.findStdValueInstantiator(ctxt.getConfig(), mapType); return new MapParameterAnyProperty(property, field, valueType, keyDeser, valueDeser, typeDeser, vi, parameterIndex); } public static SettableAnyProperty constructForJsonNodeParameter(DeserializationContext ctxt, BeanProperty prop, AnnotatedMember mutator, JavaType valueType, JsonDeserializer valueDeser, int parameterIndex ) { return new JsonNodeParameterAnyProperty(prop, mutator, valueType, valueDeser, ctxt.getNodeFactory(), parameterIndex); } // Abstract @since 2.14 public abstract SettableAnyProperty withValueDeserializer(JsonDeserializer deser); public void fixAccess(DeserializationConfig config) { _setter.fixAccess( config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS)); } /* /********************************************************** /* JDK serialization handling /********************************************************** */ /** * Need to define this to verify that we retain actual Method reference */ Object readResolve() { // sanity check... if (_setter == null || _setter.getAnnotated() == null) { throw new IllegalArgumentException("Missing method/field (broken JDK (de)serialization?)"); } return this; } /* /********************************************************** /* Public API, accessors /********************************************************** */ public BeanProperty getProperty() { return _property; } public boolean hasValueDeserializer() { return (_valueDeserializer != null); } public JavaType getType() { return _type; } /** * @since 2.14 */ public String getPropertyName() { return _property.getName(); } /** * Accessor for parameterIndex. * @return -1 if not a parameterized setter, otherwise index of parameter * * @since 2.18 */ public int getParameterIndex() { return -1; } /** * Create an instance of value to pass through Creator parameter. * * @since 2.18 */ public Object createParameterObject() { throw new UnsupportedOperationException("Cannot call createParameterObject() on " + getClass().getName()); } /* /********************************************************** /* Public API, deserialization /********************************************************** */ /** * Method called to deserialize appropriate value, given parser (and * context), and set it using appropriate method (a setter method). */ public void deserializeAndSet(JsonParser p, DeserializationContext ctxt, Object instance, String propName) throws IOException { try { Object key = (_keyDeserializer == null) ? propName : _keyDeserializer.deserializeKey(propName, ctxt); set(instance, key, deserialize(p, ctxt)); } catch (UnresolvedForwardReference reference) { if (!(_valueDeserializer.getObjectIdReader() != null)) { throw JsonMappingException.from(p, "Unresolved forward reference but no identity info.", reference); } AnySetterReferring referring = new AnySetterReferring(this, reference, _type.getRawClass(), instance, propName); reference.getRoid().appendReferring(referring); } } public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { if (p.hasToken(JsonToken.VALUE_NULL)) { return _valueDeserializer.getNullValue(ctxt); } if (_valueTypeDeserializer != null) { return _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer); } return _valueDeserializer.deserialize(p, ctxt); } // Default implementation since 2.14 public void set(Object instance, Object propName, Object value) throws IOException { try { _set(instance, propName, value); } catch (IOException e) { throw e; } catch (Exception e) { _throwAsIOE(e, propName, value); } } protected abstract void _set(Object instance, Object propName, Object value) throws Exception; /* /********************************************************** /* Helper methods /********************************************************** */ /** * @param e Exception to re-throw or wrap * @param propName Name of property (from Json input) to set * @param value Value of the property */ protected void _throwAsIOE(Exception e, Object propName, Object value) throws IOException { if (e instanceof IllegalArgumentException) { String actType = ClassUtil.classNameOf(value); StringBuilder msg = new StringBuilder("Problem deserializing \"any-property\" '").append(propName); msg.append("' of class "+getClassName()+" (expected type: ").append(_type); msg.append("; actual type: ").append(actType).append(")"); String origMsg = ClassUtil.exceptionMessage(e); if (origMsg != null) { msg.append(", problem: ").append(origMsg); } else { msg.append(" (no error message provided)"); } throw new JsonMappingException(null, msg.toString(), e); } ClassUtil.throwIfIOE(e); ClassUtil.throwIfRTE(e); // let's wrap the innermost problem Throwable t = ClassUtil.getRootCause(e); throw new JsonMappingException(null, ClassUtil.exceptionMessage(t), t); } private String getClassName() { return ClassUtil.nameOf(_setter.getDeclaringClass()); } @Override public String toString() { return "[any property on class "+getClassName()+"]"; } private static class AnySetterReferring extends Referring { private final SettableAnyProperty _parent; private final Object _pojo; private final String _propName; public AnySetterReferring(SettableAnyProperty parent, UnresolvedForwardReference reference, Class type, Object instance, String propName) { super(reference, type); _parent = parent; _pojo = instance; _propName = propName; } @Override public void handleResolvedForwardReference(Object id, Object value) throws IOException { if (!hasId(id)) { throw new IllegalArgumentException("Trying to resolve a forward reference with id [" + id.toString() + "] that wasn't previously registered."); } _parent.set(_pojo, _propName, value); } } /* /********************************************************************** /* Concrete implementations /********************************************************************** */ /** * @since 2.14 */ protected static class MethodAnyProperty extends SettableAnyProperty implements java.io.Serializable { private static final long serialVersionUID = 1L; public MethodAnyProperty(BeanProperty property, AnnotatedMember field, JavaType valueType, KeyDeserializer keyDeser, JsonDeserializer valueDeser, TypeDeserializer typeDeser) { super(property, field, valueType, keyDeser, valueDeser, typeDeser); } @Override protected void _set(Object instance, Object propName, Object value) throws Exception { // note: cannot use 'setValue()' due to taking 2 args ((AnnotatedMethod) _setter).callOnWith(instance, propName, value); } @Override public SettableAnyProperty withValueDeserializer(JsonDeserializer deser) { return new MethodAnyProperty(_property, _setter, _type, _keyDeserializer, deser, _valueTypeDeserializer); } } /** * @since 2.14 */ protected static class MapFieldAnyProperty extends SettableAnyProperty implements java.io.Serializable { private static final long serialVersionUID = 1L; protected final ValueInstantiator _valueInstantiator; public MapFieldAnyProperty(BeanProperty property, AnnotatedMember field, JavaType valueType, KeyDeserializer keyDeser, JsonDeserializer valueDeser, TypeDeserializer typeDeser, ValueInstantiator inst) { super(property, field, valueType, keyDeser, valueDeser, typeDeser); _valueInstantiator = inst; } @Override public SettableAnyProperty withValueDeserializer(JsonDeserializer deser) { return new MapFieldAnyProperty(_property, _setter, _type, _keyDeserializer, deser, _valueTypeDeserializer, _valueInstantiator); } @SuppressWarnings("unchecked") @Override protected void _set(Object instance, Object propName, Object value) throws Exception { AnnotatedField field = (AnnotatedField) _setter; Map val = (Map) field.getValue(instance); // 01-Aug-2022, tatu: [databind#3559] Will try to create and assign an // instance. if (val == null) { val = _createAndSetMap(null, field, instance, propName); } // add the property key and value val.put(propName, value); } @SuppressWarnings("unchecked") protected Map _createAndSetMap(DeserializationContext ctxt, AnnotatedField field, Object instance, Object propName) throws IOException { if (_valueInstantiator == null) { throw JsonMappingException.from(ctxt, String.format( "Cannot create an instance of %s for use as \"any-setter\" '%s'", ClassUtil.nameOf(_type.getRawClass()), _property.getName())); } Map map = (Map) _valueInstantiator.createUsingDefault(ctxt); field.setValue(instance, map); return map; } } /** * @since 2.14 */ protected static class JsonNodeFieldAnyProperty extends SettableAnyProperty implements java.io.Serializable { private static final long serialVersionUID = 1L; protected final JsonNodeFactory _nodeFactory; public JsonNodeFieldAnyProperty(BeanProperty property, AnnotatedMember field, JavaType valueType, JsonDeserializer valueDeser, JsonNodeFactory nodeFactory) { super(property, field, valueType, null, valueDeser, null); _nodeFactory = nodeFactory; } // Let's override since this is much simpler with JsonNodes @Override public void deserializeAndSet(JsonParser p, DeserializationContext ctxt, Object instance, String propName) throws IOException { setProperty(instance, propName, (JsonNode) deserialize(p, ctxt)); } // Let's override since this is much simpler with JsonNodes @Override public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { return _valueDeserializer.deserialize(p, ctxt); } @Override protected void _set(Object instance, Object propName, Object value) throws Exception { setProperty(instance, (String) propName, (JsonNode) value); } protected void setProperty(Object instance, String propName, JsonNode value) throws IOException { AnnotatedField field = (AnnotatedField) _setter; Object val0 = field.getValue(instance); ObjectNode objectNode; if (val0 == null) { objectNode = _nodeFactory.objectNode(); field.setValue(instance, objectNode); } else if (!(val0 instanceof ObjectNode)) { throw JsonMappingException.from((DeserializationContext) null, String.format( "Value \"any-setter\" '%s' not `ObjectNode` but %s", getPropertyName(), ClassUtil.nameOf(val0.getClass()))); } else { objectNode = (ObjectNode) val0; } // add the property key and value objectNode.set(propName, value); } // Should not get called but... @Override public SettableAnyProperty withValueDeserializer(JsonDeserializer deser) { return this; } } /** * [databind#562] Allow @JsonAnySetter on Creator constructor * * @since 2.18 */ protected static class MapParameterAnyProperty extends SettableAnyProperty implements java.io.Serializable { private static final long serialVersionUID = 1L; protected final ValueInstantiator _valueInstantiator; protected final int _parameterIndex; public MapParameterAnyProperty(BeanProperty property, AnnotatedMember field, JavaType valueType, KeyDeserializer keyDeser, JsonDeserializer valueDeser, TypeDeserializer typeDeser, ValueInstantiator inst, int parameterIndex) { super(property, field, valueType, keyDeser, valueDeser, typeDeser); _valueInstantiator = Objects.requireNonNull(inst, "ValueInstantiator for MapParameterAnyProperty cannot be `null`"); _parameterIndex = parameterIndex; } @Override public SettableAnyProperty withValueDeserializer(JsonDeserializer deser) { return new MapParameterAnyProperty(_property, _setter, _type, _keyDeserializer, deser, _valueTypeDeserializer, _valueInstantiator, _parameterIndex); } @SuppressWarnings("unchecked") @Override protected void _set(Object instance, Object propName, Object value) { ((Map) instance).put(propName, value); } @Override public int getParameterIndex() { return _parameterIndex; } @Override public Object createParameterObject() { return new HashMap<>(); } } /** * [databind#562] Allow @JsonAnySetter on Creator constructor * * @since 2.18 */ protected static class JsonNodeParameterAnyProperty extends SettableAnyProperty implements java.io.Serializable { private static final long serialVersionUID = 1L; protected final JsonNodeFactory _nodeFactory; protected final int _parameterIndex; public JsonNodeParameterAnyProperty(BeanProperty property, AnnotatedMember field, JavaType valueType, JsonDeserializer valueDeser, JsonNodeFactory nodeFactory, int parameterIndex) { super(property, field, valueType, null, valueDeser, null); _nodeFactory = nodeFactory; _parameterIndex = parameterIndex; } // Let's override since this is much simpler with JsonNodes @Override public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { return _valueDeserializer.deserialize(p, ctxt); } @Override protected void _set(Object instance, Object propName, Object value) throws Exception { ((ObjectNode) instance).set((String) propName, (JsonNode) value); } // Should not get called but... @Override public SettableAnyProperty withValueDeserializer(JsonDeserializer deser) { throw new UnsupportedOperationException("Cannot call withValueDeserializer() on " + getClass().getName()); } @Override public int getParameterIndex() { return _parameterIndex; } @Override public Object createParameterObject() { return _nodeFactory.objectNode(); } } }