package com.fasterxml.jackson.databind.deser.std;
import java.io.*;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Currency;
import java.util.IllformedLocaleException;
import java.util.Locale;
import java.util.TimeZone;
import java.util.regex.Pattern;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.util.VersionUtil;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.cfg.CoercionAction;
import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.util.ClassUtil;
Base class for simple deserializers that serialize values from String representation: this includes JSON Strings and other Scalar values that can be coerced into text, like Numbers and Booleans). Simple JSON String values are trimmed using String.trim
. Partial deserializer implementation will try to first access current token as a String, calls _deserialize(String,DeserializationContext)
and returns return value. If this does not work (current token not a simple scalar type), attempts are made so that:
- Embedded values (
JsonToken.VALUE_EMBEDDED_OBJECT
) are returned as-is if they are of compatible type
- Arrays may be "unwrapped" if (and only if)
DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS
is enabled, and array contains just a single scalar value that can be deserialized (for example, JSON Array with single JSON String element).
Special handling includes:
- Null values (
JsonToken.VALUE_NULL
) are handled by returning value returned by JsonDeserializer.getNullValue(DeserializationContext)
: default implementation simply returns Java `null` but this may be overridden.
- Empty String (after trimming) will result in
_deserializeFromEmptyString
getting called, and return value being returned as deserialization: default implementation simply returns `null`.
/**
* Base class for simple deserializers that serialize values from String
* representation: this includes JSON Strings and other Scalar values that
* can be coerced into text, like Numbers and Booleans).
* Simple JSON String values are trimmed using {@link java.lang.String#trim}.
* Partial deserializer implementation will try to first access current token as
* a String, calls {@code _deserialize(String,DeserializationContext)} and
* returns return value.
* If this does not work (current token not a simple scalar type), attempts
* are made so that:
*<ul>
* <li>Embedded values ({@link JsonToken#VALUE_EMBEDDED_OBJECT}) are returned as-is
* if they are of compatible type
* </li>
* <li>Arrays may be "unwrapped" if (and only if) {@link DeserializationFeature#UNWRAP_SINGLE_VALUE_ARRAYS}
* is enabled, and array contains just a single scalar value that can be deserialized
* (for example, JSON Array with single JSON String element).
* </li>
* </ul>
*<p>
* Special handling includes:
* <ul>
* <li>Null values ({@link JsonToken#VALUE_NULL}) are handled by returning value
* returned by {@link JsonDeserializer#getNullValue(DeserializationContext)}: default
* implementation simply returns Java `null` but this may be overridden.
* </li>
* <li>Empty String (after trimming) will result in {@link #_deserializeFromEmptyString}
* getting called, and return value being returned as deserialization: default implementation
* simply returns `null`.
* </li>
* </ul>
*/
@SuppressWarnings("serial")
public abstract class FromStringDeserializer<T> extends StdScalarDeserializer<T>
{
public static Class<?>[] types() {
return new Class<?>[] {
File.class,
URL.class,
URI.class,
Class.class,
JavaType.class,
Currency.class,
Pattern.class,
Locale.class,
Charset.class,
TimeZone.class,
InetAddress.class,
InetSocketAddress.class,
// Special impl:
StringBuilder.class,
};
}
/*
/**********************************************************
/* Life-cycle
/**********************************************************
*/
protected FromStringDeserializer(Class<?> vc) {
super(vc);
}
Factory method for trying to find a deserializer for one of supported
types that have simple from-String serialization.
/**
* Factory method for trying to find a deserializer for one of supported
* types that have simple from-String serialization.
*/
public static FromStringDeserializer<?> findDeserializer(Class<?> rawType)
{
int kind = 0;
if (rawType == File.class) {
kind = Std.STD_FILE;
} else if (rawType == URL.class) {
kind = Std.STD_URL;
} else if (rawType == URI.class) {
kind = Std.STD_URI;
} else if (rawType == Class.class) {
kind = Std.STD_CLASS;
} else if (rawType == JavaType.class) {
kind = Std.STD_JAVA_TYPE;
} else if (rawType == Currency.class) {
kind = Std.STD_CURRENCY;
} else if (rawType == Pattern.class) {
kind = Std.STD_PATTERN;
} else if (rawType == Locale.class) {
kind = Std.STD_LOCALE;
} else if (rawType == Charset.class) {
kind = Std.STD_CHARSET;
} else if (rawType == TimeZone.class) {
kind = Std.STD_TIME_ZONE;
} else if (rawType == InetAddress.class) {
kind = Std.STD_INET_ADDRESS;
} else if (rawType == InetSocketAddress.class) {
kind = Std.STD_INET_SOCKET_ADDRESS;
} else if (rawType == StringBuilder.class) {
return new StringBuilderDeserializer();
} else {
return null;
}
return new Std(rawType, kind);
}
@Override // since 2.12
public LogicalType logicalType() {
return LogicalType.OtherScalar;
}
/*
/**********************************************************
/* Deserializer implementations
/**********************************************************
*/
@SuppressWarnings("unchecked")
@Override
public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
// Let's get textual value, possibly via coercion from other scalar types
String text = p.getValueAsString();
if (text == null) {
JsonToken t = p.currentToken();
if (t != JsonToken.START_OBJECT) {
return (T) _deserializeFromOther(p, ctxt, t);
}
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
text = ctxt.extractScalarFromObject(p, this, _valueClass);
}
if (text.isEmpty()) {
// 09-Jun-2020, tatu: Commonly `null` but may coerce to "empty" as well
return (T) _deserializeFromEmptyString(ctxt);
}
if (_shouldTrim()) {
final String old = text;
text = text.trim();
if (text != old) {
if (text.isEmpty()) {
return (T) _deserializeFromEmptyString(ctxt);
}
}
}
Exception cause = null;
try {
// 19-May-2017, tatu: Used to require non-null result (assuming `null`
// indicated error; but that seems wrong. Should be able to return
// `null` as value.
return _deserialize(text, ctxt);
} catch (IllegalArgumentException | MalformedURLException e) {
cause = e;
}
// note: `cause` can't be null
String msg = "not a valid textual representation";
String m2 = cause.getMessage();
if (m2 != null) {
msg = msg + ", problem: "+m2;
}
// 05-May-2016, tatu: Unlike most usage, this seems legit, so...
throw ctxt.weirdStringException(text, _valueClass, msg)
.withCause(cause);
}
Main method from trying to deserialize actual value from non-empty
String.
/**
* Main method from trying to deserialize actual value from non-empty
* String.
*/
protected abstract T _deserialize(String value, DeserializationContext ctxt) throws IOException;
// @since 2.13.1
protected boolean _shouldTrim() {
return true;
}
// @since 2.12
protected Object _deserializeFromOther(JsonParser p, DeserializationContext ctxt,
JsonToken t) throws IOException
{
// [databind#381]
if (t == JsonToken.START_ARRAY) {
return _deserializeFromArray(p, ctxt);
}
if (t == JsonToken.VALUE_EMBEDDED_OBJECT) {
// Trivial cases; null to null, instance of type itself returned as is
Object ob = p.getEmbeddedObject();
if (ob == null) {
return null;
}
if (_valueClass.isAssignableFrom(ob.getClass())) {
return ob;
}
return _deserializeEmbedded(ob, ctxt);
}
return ctxt.handleUnexpectedToken(_valueClass, p);
}
Overridable method to allow coercion from embedded value that is neither null
nor directly assignable to target type. Used, for example, by UUIDDeserializer
to coerce from byte[]
. /**
* Overridable method to allow coercion from embedded value that is neither
* {@code null} nor directly assignable to target type.
* Used, for example, by {@link UUIDDeserializer} to coerce from {@code byte[]}.
*/
protected T _deserializeEmbedded(Object ob, DeserializationContext ctxt) throws IOException {
// default impl: error out
ctxt.reportInputMismatch(this,
"Don't know how to convert embedded Object of type %s into %s",
ob.getClass().getName(), _valueClass.getName());
return null;
}
@Deprecated // since 2.12 -- override variant that takes context
protected final T _deserializeFromEmptyString() throws IOException {
return null;
}
Since: 2.12
/**
* @since 2.12
*/
protected Object _deserializeFromEmptyString(DeserializationContext ctxt) throws IOException {
CoercionAction act = ctxt.findCoercionAction(logicalType(), _valueClass,
CoercionInputShape.EmptyString);
if (act == CoercionAction.Fail) {
ctxt.reportInputMismatch(this,
"Cannot coerce empty String (\"\") to %s (but could if enabling coercion using `CoercionConfig`)",
_coercedTypeDesc());
}
if (act == CoercionAction.AsNull) {
return getNullValue(ctxt);
}
if (act == CoercionAction.AsEmpty) {
return getEmptyValue(ctxt);
}
// 09-Jun-2020, tatu: semantics for `TryConvert` are bit interesting due to
// historical reasons
return _deserializeFromEmptyStringDefault(ctxt);
}
Since: 2.12
/**
* @since 2.12
*/
protected Object _deserializeFromEmptyStringDefault(DeserializationContext ctxt) throws IOException {
// by default, "as-null", but overridable by sub-classes
return getNullValue(ctxt);
}
/*
/**********************************************************
/* A general-purpose implementation
/**********************************************************
*/
"Chameleon" deserializer that works on simple types that are deserialized
from a simple String.
Since: 2.4
/**
* "Chameleon" deserializer that works on simple types that are deserialized
* from a simple String.
*
* @since 2.4
*/
public static class Std extends FromStringDeserializer<Object>
{
private static final long serialVersionUID = 1;
public final static int STD_FILE = 1;
public final static int STD_URL = 2;
public final static int STD_URI = 3;
public final static int STD_CLASS = 4;
public final static int STD_JAVA_TYPE = 5;
public final static int STD_CURRENCY = 6;
public final static int STD_PATTERN = 7;
public final static int STD_LOCALE = 8;
public final static int STD_CHARSET = 9;
public final static int STD_TIME_ZONE = 10;
public final static int STD_INET_ADDRESS = 11;
public final static int STD_INET_SOCKET_ADDRESS = 12;
// No longer implemented here since 2.12
// public final static int STD_STRING_BUILDER = 13;
protected final static String LOCALE_EXT_MARKER = "_#";
protected final int _kind;
protected Std(Class<?> valueType, int kind) {
super(valueType);
_kind = kind;
}
@Override
protected Object _deserialize(String value, DeserializationContext ctxt) throws IOException
{
switch (_kind) {
case STD_FILE:
return new File(value);
case STD_URL:
return new URL(value);
case STD_URI:
return URI.create(value);
case STD_CLASS:
try {
return ctxt.findClass(value);
} catch (Exception e) {
return ctxt.handleInstantiationProblem(_valueClass, value,
ClassUtil.getRootCause(e));
}
case STD_JAVA_TYPE:
return ctxt.getTypeFactory().constructFromCanonical(value);
case STD_CURRENCY:
// will throw IAE if unknown:
return Currency.getInstance(value);
case STD_PATTERN:
// will throw IAE (or its subclass) if malformed
return Pattern.compile(value);
case STD_LOCALE:
return _deserializeLocale(value, ctxt);
case STD_CHARSET:
return Charset.forName(value);
case STD_TIME_ZONE:
return TimeZone.getTimeZone(value);
case STD_INET_ADDRESS:
return InetAddress.getByName(value);
case STD_INET_SOCKET_ADDRESS:
if (value.startsWith("[")) {
// bracketed IPv6 (with port number)
int i = value.lastIndexOf(']');
if (i == -1) {
throw new InvalidFormatException(ctxt.getParser(),
"Bracketed IPv6 address must contain closing bracket",
value, InetSocketAddress.class);
}
int j = value.indexOf(':', i);
int port = j > -1 ? Integer.parseInt(value.substring(j + 1)) : 0;
return new InetSocketAddress(value.substring(0, i + 1), port);
}
int ix = value.indexOf(':');
if (ix >= 0 && value.indexOf(':', ix + 1) < 0) {
// host:port
int port = Integer.parseInt(value.substring(ix+1));
return new InetSocketAddress(value.substring(0, ix), port);
}
// host or unbracketed IPv6, without port number
return new InetSocketAddress(value, 0);
}
VersionUtil.throwInternal();
return null;
}
@Override // since 2.12
public Object getEmptyValue(DeserializationContext ctxt)
throws JsonMappingException
{
switch (_kind) {
case STD_URI:
// As per [databind#398], URI requires special handling
return URI.create("");
case STD_LOCALE:
// As per [databind#1123], Locale too
return Locale.ROOT;
}
return super.getEmptyValue(ctxt);
}
@Override
protected Object _deserializeFromEmptyStringDefault(DeserializationContext ctxt) throws IOException {
// 09-Jun-2020, tatu: For backwards compatibility deserialize "as-empty"
// as URI and Locale did that in 2.11 (and StringBuilder probably ought to).
// But doing this here instead of super-class lets UUID return "as-null" instead
return getEmptyValue(ctxt);
}
// @since 2.13.1
@Override
protected boolean _shouldTrim() {
// 04-Dec-2021, tatu: For [databund#3290]
return (_kind != STD_PATTERN);
}
protected int _firstHyphenOrUnderscore(String str)
{
for (int i = 0, end = str.length(); i < end; ++i) {
char c = str.charAt(i);
if (c == '_' || c == '-') {
return i;
}
}
return -1;
}
private Locale _deserializeLocale(String value, DeserializationContext ctxt)
throws IOException
{
int ix = _firstHyphenOrUnderscore(value);
if (ix < 0) { // single argument
return new Locale(value);
}
String first = value.substring(0, ix);
value = value.substring(ix+1);
ix = _firstHyphenOrUnderscore(value);
if (ix < 0) { // two pieces
return new Locale(first, value);
}
String second = value.substring(0, ix);
// [databind#3259]: Support for BCP 47 java.util.Locale ser/deser
int extMarkerIx = value.indexOf(LOCALE_EXT_MARKER);
if (extMarkerIx < 0) {
return new Locale(first, second, value.substring(ix+1));
}
return _deSerializeBCP47Locale(value, ix, first, second, extMarkerIx);
}
private Locale _deSerializeBCP47Locale(String value, int ix, String first, String second,
int extMarkerIx)
{
String third = "";
try {
// Below condition checks if variant value is present to handle empty variant values such as
// en__#Latn_x-ext
// _US_#Latn
if (extMarkerIx > 0 && extMarkerIx > ix) {
third = value.substring(ix + 1, extMarkerIx);
}
value = value.substring(extMarkerIx + 2);
if (value.indexOf('_') < 0 && value.indexOf('-') < 0) {
return new Locale.Builder().setLanguage(first)
.setRegion(second).setVariant(third).setScript(value).build();
}
if (value.indexOf('_') < 0) {
ix = value.indexOf('-');
return new Locale.Builder().setLanguage(first)
.setRegion(second).setVariant(third)
.setExtension(value.charAt(0), value.substring(ix + 1))
.build();
}
ix = value.indexOf('_');
return new Locale.Builder().setLanguage(first)
.setRegion(second).setVariant(third)
.setScript(value.substring(0, ix))
.setExtension(value.charAt(ix + 1), value.substring(ix + 3))
.build();
} catch (IllformedLocaleException ex) {
// should we really just swallow the exception?
return new Locale(first, second, third);
}
}
}
// @since 2.12 to simplify logic a bit: should not use coercions when reading
// String Values
static class StringBuilderDeserializer extends FromStringDeserializer<Object>
{
public StringBuilderDeserializer() {
super(StringBuilder.class);
}
@Override
public LogicalType logicalType() {
return LogicalType.Textual;
}
@Override
public Object getEmptyValue(DeserializationContext ctxt)
throws JsonMappingException
{
return new StringBuilder();
}
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
String text = p.getValueAsString();
if (text != null) {
return _deserialize(text, ctxt);
}
return super.deserialize(p, ctxt);
}
@Override
protected Object _deserialize(String value, DeserializationContext ctxt)
throws IOException
{
return new StringBuilder(value);
}
}
}