package com.fasterxml.jackson.databind.deser.std;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.List;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.KeyDeserializers;
import com.fasterxml.jackson.databind.introspect.AnnotatedAndMetadata;
import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.EnumResolver;

Helper class used to contain simple/well-known key deserializers. Following kinds of Objects can be handled currently:
  • Primitive wrappers (Boolean, Byte, Char, Short, Integer, Float, Long, Double)
  • Enums (usually not needed, since EnumMap doesn't call us)
  • Date
  • Calendar
  • UUID
  • Locale
  • Anything with constructor that takes a single String arg (if not explicitly @JsonIgnore'd)
  • Anything with static T valueOf(String) factory method (if not explicitly @JsonIgnore'd)
/** * Helper class used to contain simple/well-known key deserializers. * Following kinds of Objects can be handled currently: *<ul> * <li>Primitive wrappers (Boolean, Byte, Char, Short, Integer, Float, Long, Double)</li> * <li>Enums (usually not needed, since EnumMap doesn't call us)</li> * <li>{@link java.util.Date}</li> * <li>{@link java.util.Calendar}</li> * <li>{@link java.util.UUID}</li> * <li>{@link java.util.Locale}</li> * <li>Anything with constructor that takes a single String arg * (if not explicitly @JsonIgnore'd)</li> * <li>Anything with {@code static T valueOf(String)} factory method * (if not explicitly @JsonIgnore'd)</li> *</ul> */
public class StdKeyDeserializers implements KeyDeserializers, java.io.Serializable { private static final long serialVersionUID = 1L; public static KeyDeserializer constructEnumKeyDeserializer(EnumResolver enumResolver) { return new StdKeyDeserializer.EnumKD(enumResolver, null); } public static KeyDeserializer constructEnumKeyDeserializer(EnumResolver enumResolver, AnnotatedMethod factory) { return new StdKeyDeserializer.EnumKD(enumResolver, factory); } public static KeyDeserializer constructDelegatingKeyDeserializer(DeserializationConfig config, JavaType type, JsonDeserializer<?> deser) { return new StdKeyDeserializer.DelegatingKD(type.getRawClass(), deser); } public static KeyDeserializer findStringBasedKeyDeserializer(DeserializationConfig config, JavaType type) throws JsonMappingException { // 15-Jun-2021, tatu: As per [databind#3143], full introspection needs to consider // as set of possibilities. Basically, precedence is: // // 1. Explicitly annotated 1-String-arg constructor, if one exists // 2. Explicitly annotated Factory method: just one allowed (exception if multiple) // 3. Implicit 1-String-arg constructor (no visibility checks for backwards // compatibility reasons; should probably be checked in future, 3.0?) // 4. Implicit Factory method with name of "valueOf()" (primary) or // "fromString()" (secondary). Likewise, no visibility check as of yet. // We don't need full deserialization information, just need to know creators. final BeanDescription beanDesc = config.introspectForCreation(type); // Ok, so: can we find T(String) constructor? final AnnotatedAndMetadata<AnnotatedConstructor, JsonCreator.Mode> ctorInfo = _findStringConstructor(beanDesc); // Explicit? if ((ctorInfo != null) && (ctorInfo.metadata != null)) { return _constructCreatorKeyDeserializer(config, ctorInfo.annotated); } // or if not, "static T valueOf(String)" (or equivalent marked // with @JsonCreator annotation?) final List<AnnotatedAndMetadata<AnnotatedMethod, JsonCreator.Mode>> factoryCandidates = beanDesc.getFactoryMethodsWithMode(); // But must now filter out invalid candidates, both by signature (must take 1 and // only 1 arg; that arg must be of type `String`) and by annotations (we only // accept "delegating" style, so remove PROPERTIES) factoryCandidates.removeIf(m -> (m.annotated.getParameterCount() != 1) || (m.annotated.getRawParameterType(0) != String.class) || (m.metadata == JsonCreator.Mode.PROPERTIES) ); // Any explicit? final AnnotatedMethod explicitFactory = _findExplicitStringFactoryMethod(factoryCandidates); if (explicitFactory != null) { return _constructCreatorKeyDeserializer(config, explicitFactory); } // If we had implicit Constructor, that'd work now if (ctorInfo != null) { return _constructCreatorKeyDeserializer(config, ctorInfo.annotated); } // And finally, if any implicit factory methods, acceptable now // nope, no such luck... if (!factoryCandidates.isEmpty()) { // 15-Jun-2021, tatu: Ideally we would provide stabler ordering, but for now // let's simply pick the first one return _constructCreatorKeyDeserializer(config, factoryCandidates.get(0).annotated); } return null; } private static KeyDeserializer _constructCreatorKeyDeserializer(DeserializationConfig config, AnnotatedMember creator) { if (creator instanceof AnnotatedConstructor) { Constructor<?> rawCtor = ((AnnotatedConstructor) creator).getAnnotated(); if (config.canOverrideAccessModifiers()) { ClassUtil.checkAndFixAccess(rawCtor, config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS)); } return new StdKeyDeserializer.StringCtorKeyDeserializer(rawCtor); } Method m = ((AnnotatedMethod) creator).getAnnotated(); if (config.canOverrideAccessModifiers()) { ClassUtil.checkAndFixAccess(m, config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS)); } return new StdKeyDeserializer.StringFactoryKeyDeserializer(m); } // 13-Jun-2021, tatu: For now just look for constructor that takes one `String` // argument (could look for CharSequence) and hence can have just one, no dups private static AnnotatedAndMetadata<AnnotatedConstructor, JsonCreator.Mode> _findStringConstructor(BeanDescription beanDesc) { for (AnnotatedAndMetadata<AnnotatedConstructor, JsonCreator.Mode> entry : beanDesc.getConstructorsWithMode()) { // BeanDescription method does NOT filter out various types so check // it takes single argument. final AnnotatedConstructor ctor = entry.annotated; if ((ctor.getParameterCount() == 1) && (String.class == ctor.getRawParameterType(0))) { return entry; } } return null; } private static AnnotatedMethod _findExplicitStringFactoryMethod( List<AnnotatedAndMetadata<AnnotatedMethod, JsonCreator.Mode>> candidates) throws JsonMappingException { AnnotatedMethod match = null; for (AnnotatedAndMetadata<AnnotatedMethod, JsonCreator.Mode> entry : candidates) { // Note: caller has filtered out invalid candidates; all we need to check are dups if (entry.metadata != null) { if (match == null) { match = entry.annotated; } else { // 15-Jun-2021, tatu: Not optimal type or information, but has to do for now // since we do not get DeserializationContext Class<?> rawKeyType = entry.annotated.getDeclaringClass(); throw new IllegalArgumentException( "Multiple suitable annotated Creator factory methods to be used as the Key deserializer for type " +ClassUtil.nameOf(rawKeyType)); } } } return match; } /* /********************************************************** /* KeyDeserializers implementation /********************************************************** */ @Override public KeyDeserializer findKeyDeserializer(JavaType type, DeserializationConfig config, BeanDescription beanDesc) throws JsonMappingException { Class<?> raw = type.getRawClass(); // 23-Apr-2013, tatu: Map primitive types, just in case one was given if (raw.isPrimitive()) { raw = ClassUtil.wrapperType(raw); } return StdKeyDeserializer.forType(raw); } }