From f1c5f8f392069401c1daaa6601cc45cbf9f5fd34 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 27 Dec 2011 20:48:48 -0800 Subject: Refactoring to improve grouping of deserializers --- .../jackson/databind/deser/KeyDeserializers.java | 2 - .../jackson/databind/deser/StdDeserializers.java | 82 +--- .../databind/deser/std/CalendarDeserializer.java | 47 --- .../databind/deser/std/DateDeserializer.java | 28 -- .../databind/deser/std/DateDeserializers.java | 157 +++++++ .../databind/deser/std/FromStringDeserializer.java | 187 +------- .../databind/deser/std/JacksonDeserializers.java | 84 ++++ .../databind/deser/std/JavaTypeDeserializer.java | 37 -- .../databind/deser/std/JdkDeserializers.java | 272 ++++++++++++ .../databind/deser/std/NumberDeserializers.java | 445 +++++++++++++++++++ .../databind/deser/std/StdDeserializer.java | 470 +-------------------- .../deser/std/StringCollectionDeserializer.java | 10 +- .../databind/deser/std/StringDeserializer.java | 3 +- .../databind/deser/std/TimestampDeserializer.java | 28 -- .../deser/std/TokenBufferDeserializer.java | 36 -- .../jackson/databind/deser/TestAnyProperties.java | 33 ++ .../introspect/TestPOJOPropertiesCollector.java | 33 ++ 17 files changed, 1057 insertions(+), 897 deletions(-) delete mode 100644 src/main/java/com/fasterxml/jackson/databind/deser/std/CalendarDeserializer.java delete mode 100644 src/main/java/com/fasterxml/jackson/databind/deser/std/DateDeserializer.java create mode 100644 src/main/java/com/fasterxml/jackson/databind/deser/std/DateDeserializers.java create mode 100644 src/main/java/com/fasterxml/jackson/databind/deser/std/JacksonDeserializers.java delete mode 100644 src/main/java/com/fasterxml/jackson/databind/deser/std/JavaTypeDeserializer.java create mode 100644 src/main/java/com/fasterxml/jackson/databind/deser/std/JdkDeserializers.java create mode 100644 src/main/java/com/fasterxml/jackson/databind/deser/std/NumberDeserializers.java delete mode 100644 src/main/java/com/fasterxml/jackson/databind/deser/std/TimestampDeserializer.java delete mode 100644 src/main/java/com/fasterxml/jackson/databind/deser/std/TokenBufferDeserializer.java diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/KeyDeserializers.java b/src/main/java/com/fasterxml/jackson/databind/deser/KeyDeserializers.java index 141648f7c..5dad1d0c0 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/KeyDeserializers.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/KeyDeserializers.java @@ -16,8 +16,6 @@ import com.fasterxml.jackson.databind.KeyDeserializer; * does not support handling of the type. In latter case, further calls can be made * for other providers; in former case returned key deserializer is used for handling of * key instances of specified type. - * - * @since 1.8 */ public interface KeyDeserializers { diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/StdDeserializers.java b/src/main/java/com/fasterxml/jackson/databind/deser/StdDeserializers.java index 4cca7f76e..07f587ad4 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/StdDeserializers.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/StdDeserializers.java @@ -3,25 +3,11 @@ package com.fasterxml.jackson.databind.deser; import java.util.*; import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.deser.std.AtomicBooleanDeserializer; -import com.fasterxml.jackson.databind.deser.std.CalendarDeserializer; -import com.fasterxml.jackson.databind.deser.std.ClassDeserializer; -import com.fasterxml.jackson.databind.deser.std.DateDeserializer; -import com.fasterxml.jackson.databind.deser.std.FromStringDeserializer; -import com.fasterxml.jackson.databind.deser.std.JavaTypeDeserializer; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import com.fasterxml.jackson.databind.deser.std.StringDeserializer; -import com.fasterxml.jackson.databind.deser.std.TimestampDeserializer; -import com.fasterxml.jackson.databind.deser.std.TokenBufferDeserializer; -import com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer; +import com.fasterxml.jackson.databind.deser.std.*; import com.fasterxml.jackson.databind.type.*; /** * Helper class used to contain simple/well-known deserializers for core JDK types. - *

- * Note: as of Jackson 1.9, we use type-erased class for registering, since - * some types may come either as type-erased or typed (for example, - * java.lang.Class). */ class StdDeserializers { @@ -37,62 +23,19 @@ class StdDeserializers StdDeserializer strDeser = new StringDeserializer(); add(strDeser, String.class); add(strDeser, CharSequence.class); - add(new ClassDeserializer()); - // Then primitive-wrappers (simple): - add(new StdDeserializer.BooleanDeserializer(Boolean.class, null)); - add(new StdDeserializer.ByteDeserializer(Byte.class, null)); - add(new StdDeserializer.ShortDeserializer(Short.class, null)); - add(new StdDeserializer.CharacterDeserializer(Character.class, null)); - add(new StdDeserializer.IntegerDeserializer(Integer.class, null)); - add(new StdDeserializer.LongDeserializer(Long.class, null)); - add(new StdDeserializer.FloatDeserializer(Float.class, null)); - add(new StdDeserializer.DoubleDeserializer(Double.class, null)); - - /* And actual primitives: difference is the way nulls are to be - * handled... - */ - add(new StdDeserializer.BooleanDeserializer(Boolean.TYPE, Boolean.FALSE)); - add(new StdDeserializer.ByteDeserializer(Byte.TYPE, Byte.valueOf((byte)(0)))); - add(new StdDeserializer.ShortDeserializer(Short.TYPE, Short.valueOf((short)0))); - add(new StdDeserializer.CharacterDeserializer(Character.TYPE, Character.valueOf('\0'))); - add(new StdDeserializer.IntegerDeserializer(Integer.TYPE, Integer.valueOf(0))); - add(new StdDeserializer.LongDeserializer(Long.TYPE, Long.valueOf(0L))); - add(new StdDeserializer.FloatDeserializer(Float.TYPE, Float.valueOf(0.0f))); - add(new StdDeserializer.DoubleDeserializer(Double.TYPE, Double.valueOf(0.0))); - - // and related - add(new StdDeserializer.NumberDeserializer()); - add(new StdDeserializer.BigDecimalDeserializer()); - add(new StdDeserializer.BigIntegerDeserializer()); - - add(new CalendarDeserializer()); - add(new DateDeserializer()); - /* 24-Jan-2010, tatu: When including type information, we may - * know that we specifically need GregorianCalendar... - */ - add(new CalendarDeserializer(GregorianCalendar.class), - GregorianCalendar.class); - add(new StdDeserializer.SqlDateDeserializer()); - add(new TimestampDeserializer()); - - // From-string deserializers: - for (StdDeserializer deser : FromStringDeserializer.all()) { - add(deser); - } - - // And finally some odds and ends - - // to deserialize Throwable, need stack trace elements: - add(new StdDeserializer.StackTraceElementDeserializer()); + // Primitives/wrappers, other Numbers: + add(NumberDeserializers.all()); + // Date/time types + add(DateDeserializers.all()); + // other JDK types + add(JdkDeserializers.all()); // [JACKSON-283] need to support atomic types, too // (note: AtomicInteger/Long work due to single-arg constructor) add(new AtomicBooleanDeserializer()); - // including some core Jackson types: - add(new TokenBufferDeserializer()); - add(new JavaTypeDeserializer()); + add(JacksonDeserializers.all()); } /** @@ -103,8 +46,13 @@ class StdDeserializers return new StdDeserializers()._deserializers; } - private void add(StdDeserializer stdDeser) - { + private void add(StdDeserializer[] serializers) { + for (StdDeserializer ser : serializers) { + add(ser, ser.getValueClass()); + } + } + + private void add(StdDeserializer stdDeser) { add(stdDeser, stdDeser.getValueClass()); } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/CalendarDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/CalendarDeserializer.java deleted file mode 100644 index 8f7151196..000000000 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/CalendarDeserializer.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.fasterxml.jackson.databind.deser.std; - -import java.io.IOException; -import java.util.Calendar; -import java.util.Date; - -import com.fasterxml.jackson.core.*; - -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; - -@JacksonStdImpl -public class CalendarDeserializer - extends StdScalarDeserializer -{ - /** - * We may know actual expected type; if so, it will be - * used for instantiation. - */ - protected final Class _calendarClass; - - public CalendarDeserializer() { this(null); } - public CalendarDeserializer(Class cc) { - super(Calendar.class); - _calendarClass = cc; - } - - @Override - public Calendar deserialize(JsonParser jp, DeserializationContext ctxt) - throws IOException, JsonProcessingException - { - Date d = _parseDate(jp, ctxt); - if (d == null) { - return null; - } - if (_calendarClass == null) { - return ctxt.constructCalendar(d); - } - try { - Calendar c = _calendarClass.newInstance(); - c.setTimeInMillis(d.getTime()); - return c; - } catch (Exception e) { - throw ctxt.instantiationException(_calendarClass, e); - } - } -} diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/DateDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/DateDeserializer.java deleted file mode 100644 index 6a17b120f..000000000 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/DateDeserializer.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.fasterxml.jackson.databind.deser.std; - -import java.io.IOException; -import java.util.Date; - -import com.fasterxml.jackson.core.*; - -import com.fasterxml.jackson.databind.DeserializationContext; - -/** - * Simple deserializer for handling {@link java.util.Date} values. - *

- * One way to customize Date formats accepted is to override method - * {@link DeserializationContext#parseDate} that this basic - * deserializer calls. - */ -public class DateDeserializer - extends StdScalarDeserializer -{ - public DateDeserializer() { super(Date.class); } - - @Override - public java.util.Date deserialize(JsonParser jp, DeserializationContext ctxt) - throws IOException, JsonProcessingException - { - return _parseDate(jp, ctxt); - } -} diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/DateDeserializers.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/DateDeserializers.java new file mode 100644 index 000000000..4e6c56d22 --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/DateDeserializers.java @@ -0,0 +1,157 @@ +package com.fasterxml.jackson.databind.deser.std; + +import java.io.IOException; +import java.sql.Timestamp; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; + +/** + * Container class for core JDK date/time type deserializers. + */ +public class DateDeserializers +{ + public static StdDeserializer[] all() + { + return new StdDeserializer[] { + new CalendarDeserializer(), // for nominal type of java.util.Calendar + new DateDeserializer(), + /* 24-Jan-2010, tatu: When including type information, we may + * know that we specifically need GregorianCalendar... + */ + new CalendarDeserializer(GregorianCalendar.class), + new SqlDateDeserializer(), + new TimestampDeserializer(), + new TimeZoneDeserializer() + }; + } + + /* + /********************************************************** + /* Deserializer implementations + /********************************************************** + */ + + @JacksonStdImpl + public static class CalendarDeserializer + extends StdScalarDeserializer + { + /** + * We may know actual expected type; if so, it will be + * used for instantiation. + */ + protected final Class _calendarClass; + + public CalendarDeserializer() { + super(Calendar.class); + _calendarClass = null; + } + + public CalendarDeserializer(Class cc) { + super(cc); + _calendarClass = cc; + } + + @Override + public Calendar deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException + { + Date d = _parseDate(jp, ctxt); + if (d == null) { + return null; + } + if (_calendarClass == null) { + return ctxt.constructCalendar(d); + } + try { + Calendar c = _calendarClass.newInstance(); + c.setTimeInMillis(d.getTime()); + return c; + } catch (Exception e) { + throw ctxt.instantiationException(_calendarClass, e); + } + } + } + + /** + * Simple deserializer for handling {@link java.util.Date} values. + *

+ * One way to customize Date formats accepted is to override method + * {@link DeserializationContext#parseDate} that this basic + * deserializer calls. + */ + public static class DateDeserializer + extends StdScalarDeserializer + { + public DateDeserializer() { super(Date.class); } + + @Override + public java.util.Date deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException + { + return _parseDate(jp, ctxt); + } + } + + /** + * Compared to plain old {@link java.util.Date}, SQL version is easier + * to deal with: mostly because it is more limited. + */ + public static class SqlDateDeserializer + extends StdScalarDeserializer + { + public SqlDateDeserializer() { super(java.sql.Date.class); } + + @Override + public java.sql.Date deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException + { + Date d = _parseDate(jp, ctxt); + return (d == null) ? null : new java.sql.Date(d.getTime()); + } + } + + /** + * Simple deserializer for handling {@link java.sql.Timestamp} values. + *

+ * One way to customize Timestamp formats accepted is to override method + * {@link DeserializationContext#parseDate} that this basic + * deserializer calls. + */ + public static class TimestampDeserializer + extends StdScalarDeserializer + { + public TimestampDeserializer() { super(Timestamp.class); } + + @Override + public java.sql.Timestamp deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException + { + return new Timestamp(_parseDate(jp, ctxt).getTime()); + } + } + + /** + * As per [JACKSON-522], also need special handling for TimeZones + * + * @since 1.7.4 + */ + protected static class TimeZoneDeserializer + extends FromStringDeserializer + { + public TimeZoneDeserializer() { super(TimeZone.class); } + + @Override + protected TimeZone _deserialize(String value, DeserializationContext ctxt) + throws IOException + { + return TimeZone.getTimeZone(value); + } + } +} diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/FromStringDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/FromStringDeserializer.java index 6f9eaf87a..660ec3d59 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/FromStringDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/FromStringDeserializer.java @@ -1,21 +1,14 @@ package com.fasterxml.jackson.databind.deser.std; import java.io.*; -import java.net.InetAddress; -import java.net.URI; -import java.net.URL; -import java.util.*; -import java.util.regex.Pattern; import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.databind.DeserializationContext; /** - * Base class for simple deserializer which only accept JSON String + * Base class for simple deserializers that only accept JSON String * values as the source. - * - * @since 1.9 (moved from higher-level package) */ public abstract class FromStringDeserializer extends StdScalarDeserializer @@ -24,23 +17,11 @@ public abstract class FromStringDeserializer super(vc); } - public static Iterable>all() - { - ArrayList> all = new ArrayList>(); - - all.add(new UUIDDeserializer()); - all.add(new URLDeserializer()); - all.add(new URIDeserializer()); - all.add(new CurrencyDeserializer()); - all.add(new PatternDeserializer()); - // since 1.7: - all.add(new LocaleDeserializer()); - // 1.8: - all.add(new InetAddressDeserializer()); - all.add(new TimeZoneDeserializer()); - - return all; - } + /* + /********************************************************** + /* Deserializer implementations + /********************************************************** + */ @SuppressWarnings("unchecked") @Override @@ -87,161 +68,5 @@ public abstract class FromStringDeserializer throw ctxt.mappingException("Don't know how to convert embedded Object of type " +ob.getClass().getName()+" into "+_valueClass.getName()); } - - /* - /********************************************************** - /* Then concrete implementations - /********************************************************** - */ - - public static class UUIDDeserializer - extends FromStringDeserializer - { - public UUIDDeserializer() { super(UUID.class); } - @Override - protected UUID _deserialize(String value, DeserializationContext ctxt) - throws IOException, JsonProcessingException - { - return UUID.fromString(value); - } - - @Override - protected UUID _deserializeEmbedded(Object ob, DeserializationContext ctxt) - throws IOException, JsonProcessingException - { - if (ob instanceof byte[]) { - byte[] bytes = (byte[]) ob; - if (bytes.length != 16) { - ctxt.mappingException("Can only construct UUIDs from 16 byte arrays; got "+bytes.length+" bytes"); - } - // clumsy, but should work for now... - DataInputStream in = new DataInputStream(new ByteArrayInputStream(bytes)); - long l1 = in.readLong(); - long l2 = in.readLong(); - return new UUID(l1, l2); - } - super._deserializeEmbedded(ob, ctxt); - return null; // never gets here - } - } - - public static class URLDeserializer - extends FromStringDeserializer - { - public URLDeserializer() { super(URL.class); } - - @Override - protected URL _deserialize(String value, DeserializationContext ctxt) - throws IOException - { - return new URL(value); - } - } - - public static class URIDeserializer - extends FromStringDeserializer - { - public URIDeserializer() { super(URI.class); } - - @Override - protected URI _deserialize(String value, DeserializationContext ctxt) - throws IllegalArgumentException - { - return URI.create(value); - } - } - - public static class CurrencyDeserializer - extends FromStringDeserializer - { - public CurrencyDeserializer() { super(Currency.class); } - - @Override - protected Currency _deserialize(String value, DeserializationContext ctxt) - throws IllegalArgumentException - { - // will throw IAE if unknown: - return Currency.getInstance(value); - } - } - - public static class PatternDeserializer - extends FromStringDeserializer - { - public PatternDeserializer() { super(Pattern.class); } - - @Override - protected Pattern _deserialize(String value, DeserializationContext ctxt) - throws IllegalArgumentException - { - // will throw IAE (or its subclass) if malformed - return Pattern.compile(value); - } - } - - /** - * Kept protected as it's not meant to be extensible at this point - * - * @since 1.7 - */ - protected static class LocaleDeserializer - extends FromStringDeserializer - { - public LocaleDeserializer() { super(Locale.class); } - - @Override - protected Locale _deserialize(String value, DeserializationContext ctxt) - throws IOException - { - int ix = value.indexOf('_'); - if (ix < 0) { // single argument - return new Locale(value); - } - String first = value.substring(0, ix); - value = value.substring(ix+1); - ix = value.indexOf('_'); - if (ix < 0) { // two pieces - return new Locale(first, value); - } - String second = value.substring(0, ix); - return new Locale(first, second, value.substring(ix+1)); - } - } - - /** - * As per [JACKSON-484], also need special handling for InetAddress... - * - * @since 1.7.4 - */ - protected static class InetAddressDeserializer - extends FromStringDeserializer - { - public InetAddressDeserializer() { super(InetAddress.class); } - - @Override - protected InetAddress _deserialize(String value, DeserializationContext ctxt) - throws IOException - { - return InetAddress.getByName(value); - } - } - - /** - * As per [JACKSON-522], also need special handling for InetAddress... - * - * @since 1.7.4 - */ - protected static class TimeZoneDeserializer - extends FromStringDeserializer - { - public TimeZoneDeserializer() { super(TimeZone.class); } - - @Override - protected TimeZone _deserialize(String value, DeserializationContext ctxt) - throws IOException - { - return TimeZone.getTimeZone(value); - } - } } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/JacksonDeserializers.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/JacksonDeserializers.java new file mode 100644 index 000000000..7e294e8c1 --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/JacksonDeserializers.java @@ -0,0 +1,84 @@ +package com.fasterxml.jackson.databind.deser.std; + +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; +import com.fasterxml.jackson.databind.util.TokenBuffer; + +/** + * Container class for core Jackson type deserializers. + */ +public class JacksonDeserializers +{ + public static StdDeserializer[] all() + { + return new StdDeserializer[] { + new TokenBufferDeserializer(), + new JavaTypeDeserializer() + }; + } + + /* + /********************************************************** + /* Deserializer implementations + /********************************************************** + */ + + /** + * Deserializer for {@link JavaType} values. + */ + public static class JavaTypeDeserializer + extends StdScalarDeserializer + { + public JavaTypeDeserializer() { super(JavaType.class); } + + @Override + public JavaType deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException + { + JsonToken curr = jp.getCurrentToken(); + // Usually should just get string value: + if (curr == JsonToken.VALUE_STRING) { + String str = jp.getText().trim(); + if (str.length() == 0) { + return getEmptyValue(); + } + return ctxt.getTypeFactory().constructFromCanonical(str); + } + // or occasionally just embedded object maybe + if (curr == JsonToken.VALUE_EMBEDDED_OBJECT) { + return (JavaType) jp.getEmbeddedObject(); + } + throw ctxt.mappingException(_valueClass); + } + } + + /** + * We also want to directly support deserialization of {@link TokenBuffer}. + *

+ * Note that we use scalar deserializer base just because we claim + * to be of scalar for type information inclusion purposes; actual + * underlying content can be of any (Object, Array, scalar) type. + */ + @JacksonStdImpl + public static class TokenBufferDeserializer + extends StdScalarDeserializer + { + public TokenBufferDeserializer() { super(TokenBuffer.class); } + + @Override + public TokenBuffer deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException + { + TokenBuffer tb = new TokenBuffer(jp.getCodec()); + // quite simple, given that TokenBuffer is a JsonGenerator: + tb.copyCurrentStructure(jp); + return tb; + } + } +} diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/JavaTypeDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/JavaTypeDeserializer.java deleted file mode 100644 index 9c4e7f170..000000000 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/JavaTypeDeserializer.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.fasterxml.jackson.databind.deser.std; - -import java.io.IOException; - -import com.fasterxml.jackson.core.*; - -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JavaType; - -/** - * @since 1.9 - */ -public class JavaTypeDeserializer - extends StdScalarDeserializer -{ - public JavaTypeDeserializer() { super(JavaType.class); } - - @Override - public JavaType deserialize(JsonParser jp, DeserializationContext ctxt) - throws IOException, JsonProcessingException - { - JsonToken curr = jp.getCurrentToken(); - // Usually should just get string value: - if (curr == JsonToken.VALUE_STRING) { - String str = jp.getText().trim(); - if (str.length() == 0) { - return getEmptyValue(); - } - return ctxt.getTypeFactory().constructFromCanonical(str); - } - // or occasionally just embedded object maybe - if (curr == JsonToken.VALUE_EMBEDDED_OBJECT) { - return (JavaType) jp.getEmbeddedObject(); - } - throw ctxt.mappingException(_valueClass); - } -} diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/JdkDeserializers.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/JdkDeserializers.java new file mode 100644 index 000000000..63a85ca0c --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/JdkDeserializers.java @@ -0,0 +1,272 @@ +package com.fasterxml.jackson.databind.deser.std; + +import java.io.*; +import java.net.InetAddress; +import java.net.URI; +import java.net.URL; +import java.util.*; +import java.util.regex.Pattern; + +import com.fasterxml.jackson.core.Base64Variants; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; +import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; + +public class JdkDeserializers +{ + public static StdDeserializer[] all() + { + return new StdDeserializer[] { + + // from String types: + new StringDeserializer(), + new UUIDDeserializer(), + new URLDeserializer(), + new URIDeserializer(), + new CurrencyDeserializer(), + new PatternDeserializer(), + new LocaleDeserializer(), + new InetAddressDeserializer(), + + // other types: + new ClassDeserializer(), + new StackTraceElementDeserializer() + }; + } + + /* + /********************************************************** + /* Deserializer implementations: from-String deserializers + /********************************************************** + */ + + /** + * Note: final as performance optimization: not expected to need sub-classing; + * if sub-classing was needed could re-factor into reusable part, final + * "Impl" sub-class + */ + @JacksonStdImpl + public final static class StringDeserializer + extends StdScalarDeserializer + { + public StringDeserializer() { super(String.class); } + + @Override + public String deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException + { + JsonToken curr = jp.getCurrentToken(); + // Usually should just get string value: + if (curr == JsonToken.VALUE_STRING) { + return jp.getText(); + } + // [JACKSON-330]: need to gracefully handle byte[] data, as base64 + if (curr == JsonToken.VALUE_EMBEDDED_OBJECT) { + Object ob = jp.getEmbeddedObject(); + if (ob == null) { + return null; + } + if (ob instanceof byte[]) { + return Base64Variants.getDefaultVariant().encode((byte[]) ob, false); + } + // otherwise, try conversion using toString()... + return ob.toString(); + } + // Can deserialize any scalar value, but not markers + if (curr.isScalarValue()) { + return jp.getText(); + } + throw ctxt.mappingException(_valueClass, curr); + } + + // 1.6: since we can never have type info ("natural type"; String, Boolean, Integer, Double): + // (is it an error to even call this version?) + @Override + public String deserializeWithType(JsonParser jp, DeserializationContext ctxt, + TypeDeserializer typeDeserializer) + throws IOException, JsonProcessingException + { + return deserialize(jp, ctxt); + } + } + + public static class UUIDDeserializer + extends FromStringDeserializer + { + public UUIDDeserializer() { super(UUID.class); } + + @Override + protected UUID _deserialize(String value, DeserializationContext ctxt) + throws IOException, JsonProcessingException + { + return UUID.fromString(value); + } + + @Override + protected UUID _deserializeEmbedded(Object ob, DeserializationContext ctxt) + throws IOException, JsonProcessingException + { + if (ob instanceof byte[]) { + byte[] bytes = (byte[]) ob; + if (bytes.length != 16) { + ctxt.mappingException("Can only construct UUIDs from 16 byte arrays; got "+bytes.length+" bytes"); + } + // clumsy, but should work for now... + DataInputStream in = new DataInputStream(new ByteArrayInputStream(bytes)); + long l1 = in.readLong(); + long l2 = in.readLong(); + return new UUID(l1, l2); + } + super._deserializeEmbedded(ob, ctxt); + return null; // never gets here + } + } + + public static class URLDeserializer + extends FromStringDeserializer + { + public URLDeserializer() { super(URL.class); } + + @Override + protected URL _deserialize(String value, DeserializationContext ctxt) + throws IOException + { + return new URL(value); + } + } + + public static class URIDeserializer + extends FromStringDeserializer + { + public URIDeserializer() { super(URI.class); } + + @Override + protected URI _deserialize(String value, DeserializationContext ctxt) + throws IllegalArgumentException + { + return URI.create(value); + } + } + + public static class CurrencyDeserializer + extends FromStringDeserializer + { + public CurrencyDeserializer() { super(Currency.class); } + + @Override + protected Currency _deserialize(String value, DeserializationContext ctxt) + throws IllegalArgumentException + { + // will throw IAE if unknown: + return Currency.getInstance(value); + } + } + + public static class PatternDeserializer + extends FromStringDeserializer + { + public PatternDeserializer() { super(Pattern.class); } + + @Override + protected Pattern _deserialize(String value, DeserializationContext ctxt) + throws IllegalArgumentException + { + // will throw IAE (or its subclass) if malformed + return Pattern.compile(value); + } + } + + /** + * Kept protected as it's not meant to be extensible at this point + */ + protected static class LocaleDeserializer + extends FromStringDeserializer + { + public LocaleDeserializer() { super(Locale.class); } + + @Override + protected Locale _deserialize(String value, DeserializationContext ctxt) + throws IOException + { + int ix = value.indexOf('_'); + if (ix < 0) { // single argument + return new Locale(value); + } + String first = value.substring(0, ix); + value = value.substring(ix+1); + ix = value.indexOf('_'); + if (ix < 0) { // two pieces + return new Locale(first, value); + } + String second = value.substring(0, ix); + return new Locale(first, second, value.substring(ix+1)); + } + } + + /** + * As per [JACKSON-484], also need special handling for InetAddress... + */ + protected static class InetAddressDeserializer + extends FromStringDeserializer + { + public InetAddressDeserializer() { super(InetAddress.class); } + + @Override + protected InetAddress _deserialize(String value, DeserializationContext ctxt) + throws IOException + { + return InetAddress.getByName(value); + } + } + + /* + /********************************************************** + /* Deserializers for other JDK types + /********************************************************** + */ + + public static class StackTraceElementDeserializer + extends StdScalarDeserializer + { + public StackTraceElementDeserializer() { super(StackTraceElement.class); } + + @Override + public StackTraceElement deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException + { + JsonToken t = jp.getCurrentToken(); + // Must get an Object + if (t == JsonToken.START_OBJECT) { + String className = "", methodName = "", fileName = ""; + int lineNumber = -1; + + while ((t = jp.nextValue()) != JsonToken.END_OBJECT) { + String propName = jp.getCurrentName(); + if ("className".equals(propName)) { + className = jp.getText(); + } else if ("fileName".equals(propName)) { + fileName = jp.getText(); + } else if ("lineNumber".equals(propName)) { + if (t.isNumeric()) { + lineNumber = jp.getIntValue(); + } else { + throw JsonMappingException.from(jp, "Non-numeric token ("+t+") for property 'lineNumber'"); + } + } else if ("methodName".equals(propName)) { + methodName = jp.getText(); + } else if ("nativeMethod".equals(propName)) { + // no setter, not passed via constructor: ignore + } else { + handleUnknownProperty(jp, ctxt, _valueClass, propName); + } + } + return new StackTraceElement(className, methodName, fileName, lineNumber); + } + throw ctxt.mappingException(_valueClass, t); + } + } +} diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/NumberDeserializers.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/NumberDeserializers.java new file mode 100644 index 000000000..83b422b9f --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/NumberDeserializers.java @@ -0,0 +1,445 @@ +package com.fasterxml.jackson.databind.deser.std; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationConfig; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; +import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; + +/** + * Container class for deserializers that handle core JDK primitive + * (and matching wrapper) types, as well as standard "big" numeric types. + * Note that this includes types such as {@link java.lang.Boolean} + * and {@link java.lang.Character} which are not strictly numeric, + * but are part of primitive/wrapper types. + */ +public class NumberDeserializers +{ + public static StdDeserializer[] all() + { + return new StdDeserializer[] { + // primitive-wrappers (simple): + new BooleanDeserializer(Boolean.class, null), + new ByteDeserializer(Byte.class, null), + new ShortDeserializer(Short.class, null), + new CharacterDeserializer(Character.class, null), + new IntegerDeserializer(Integer.class, null), + new LongDeserializer(Long.class, null), + new FloatDeserializer(Float.class, null), + new DoubleDeserializer(Double.class, null), + + /* And actual primitives: difference is the way nulls are to be + * handled... + */ + new BooleanDeserializer(Boolean.TYPE, Boolean.FALSE), + new ByteDeserializer(Byte.TYPE, Byte.valueOf((byte)(0))), + new ShortDeserializer(Short.TYPE, Short.valueOf((short)0)), + new CharacterDeserializer(Character.TYPE, Character.valueOf('\0')), + new IntegerDeserializer(Integer.TYPE, Integer.valueOf(0)), + new LongDeserializer(Long.TYPE, Long.valueOf(0L)), + new FloatDeserializer(Float.TYPE, Float.valueOf(0.0f)), + new DoubleDeserializer(Double.TYPE, Double.valueOf(0.0)), + + // and related + new NumberDeserializer(), + new BigDecimalDeserializer(), + new BigIntegerDeserializer() + }; + } + + /* + /********************************************************** + /* Then one intermediate base class for things that have + /* both primitive and wrapper types + /********************************************************** + */ + + protected abstract static class PrimitiveOrWrapperDeserializer + extends StdScalarDeserializer + { + final T _nullValue; + + protected PrimitiveOrWrapperDeserializer(Class vc, T nvl) + { + super(vc); + _nullValue = nvl; + } + + @Override + public final T getNullValue() { + return _nullValue; + } + } + + /* + /********************************************************** + /* Then primitive/wrapper types + /********************************************************** + */ + + @JacksonStdImpl + public final static class BooleanDeserializer + extends PrimitiveOrWrapperDeserializer + { + public BooleanDeserializer(Class cls, Boolean nvl) + { + super(cls, nvl); + } + + @Override + public Boolean deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException + { + return _parseBoolean(jp, ctxt); + } + + // 1.6: since we can never have type info ("natural type"; String, Boolean, Integer, Double): + // (is it an error to even call this version?) + @Override + public Boolean deserializeWithType(JsonParser jp, DeserializationContext ctxt, + TypeDeserializer typeDeserializer) + throws IOException, JsonProcessingException + { + return _parseBoolean(jp, ctxt); + } + } + + @JacksonStdImpl + public final static class ByteDeserializer + extends PrimitiveOrWrapperDeserializer + { + public ByteDeserializer(Class cls, Byte nvl) + { + super(cls, nvl); + } + + @Override + public Byte deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException + { + return _parseByte(jp, ctxt); + } + } + + @JacksonStdImpl + public final static class ShortDeserializer + extends PrimitiveOrWrapperDeserializer + { + public ShortDeserializer(Class cls, Short nvl) + { + super(cls, nvl); + } + + @Override + public Short deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException + { + return _parseShort(jp, ctxt); + } + } + + @JacksonStdImpl + public final static class CharacterDeserializer + extends PrimitiveOrWrapperDeserializer + { + public CharacterDeserializer(Class cls, Character nvl) + { + super(cls, nvl); + } + + @Override + public Character deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException + { + JsonToken t = jp.getCurrentToken(); + int value; + + if (t == JsonToken.VALUE_NUMBER_INT) { // ok iff ascii value + value = jp.getIntValue(); + if (value >= 0 && value <= 0xFFFF) { + return Character.valueOf((char) value); + } + } else if (t == JsonToken.VALUE_STRING) { // this is the usual type + // But does it have to be exactly one char? + String text = jp.getText(); + if (text.length() == 1) { + return Character.valueOf(text.charAt(0)); + } + // actually, empty should become null? + if (text.length() == 0) { + return (Character) getEmptyValue(); + } + } + throw ctxt.mappingException(_valueClass, t); + } + } + + @JacksonStdImpl + public final static class IntegerDeserializer + extends PrimitiveOrWrapperDeserializer + { + public IntegerDeserializer(Class cls, Integer nvl) + { + super(cls, nvl); + } + + @Override + public Integer deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException + { + return _parseInteger(jp, ctxt); + } + + // 1.6: since we can never have type info ("natural type"; String, Boolean, Integer, Double): + // (is it an error to even call this version?) + @Override + public Integer deserializeWithType(JsonParser jp, DeserializationContext ctxt, + TypeDeserializer typeDeserializer) + throws IOException, JsonProcessingException + { + return _parseInteger(jp, ctxt); + } + } + + @JacksonStdImpl + public final static class LongDeserializer + extends PrimitiveOrWrapperDeserializer + { + public LongDeserializer(Class cls, Long nvl) + { + super(cls, nvl); + } + + @Override + public Long deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException + { + return _parseLong(jp, ctxt); + } + } + + @JacksonStdImpl + public final static class FloatDeserializer + extends PrimitiveOrWrapperDeserializer + { + public FloatDeserializer(Class cls, Float nvl) + { + super(cls, nvl); + } + + @Override + public Float deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException + { + /* 22-Jan-2009, tatu: Bounds/range checks would be tricky + * here, so let's not bother even trying... + */ + return _parseFloat(jp, ctxt); + } + } + + @JacksonStdImpl + public final static class DoubleDeserializer + extends PrimitiveOrWrapperDeserializer + { + public DoubleDeserializer(Class cls, Double nvl) + { + super(cls, nvl); + } + + @Override + public Double deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException + { + return _parseDouble(jp, ctxt); + } + + // 1.6: since we can never have type info ("natural type"; String, Boolean, Integer, Double): + // (is it an error to even call this version?) + @Override + public Double deserializeWithType(JsonParser jp, DeserializationContext ctxt, + TypeDeserializer typeDeserializer) + throws IOException, JsonProcessingException + { + return _parseDouble(jp, ctxt); + } + } + + /** + * For type Number.class, we can just rely on type + * mappings that plain {@link JsonParser#getNumberValue} returns. + *

+ * Since 1.5, there is one additional complication: some numeric + * types (specifically, int/Integer and double/Double) are "non-typed"; + * meaning that they will NEVER be output with type information. + * But other numeric types may need such type information. + * This is why {@link #deserializeWithType} must be overridden. + */ + @JacksonStdImpl + public final static class NumberDeserializer + extends StdScalarDeserializer + { + public NumberDeserializer() { super(Number.class); } + + @Override + public Number deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException + { + JsonToken t = jp.getCurrentToken(); + if (t == JsonToken.VALUE_NUMBER_INT) { + if (ctxt.isEnabled(DeserializationConfig.Feature.USE_BIG_INTEGER_FOR_INTS)) { + return jp.getBigIntegerValue(); + } + return jp.getNumberValue(); + } else if (t == JsonToken.VALUE_NUMBER_FLOAT) { + /* [JACKSON-72]: need to allow overriding the behavior + * regarding which type to use + */ + if (ctxt.isEnabled(DeserializationConfig.Feature.USE_BIG_DECIMAL_FOR_FLOATS)) { + return jp.getDecimalValue(); + } + return Double.valueOf(jp.getDoubleValue()); + } + + /* Textual values are more difficult... not parsing itself, but figuring + * out 'minimal' type to use + */ + if (t == JsonToken.VALUE_STRING) { // let's do implicit re-parse + String text = jp.getText().trim(); + try { + if (text.indexOf('.') >= 0) { // floating point + // as per [JACKSON-72]: + if (ctxt.isEnabled(DeserializationConfig.Feature.USE_BIG_DECIMAL_FOR_FLOATS)) { + return new BigDecimal(text); + } + return new Double(text); + } + // as per [JACKSON-100]: + if (ctxt.isEnabled(DeserializationConfig.Feature.USE_BIG_INTEGER_FOR_INTS)) { + return new BigInteger(text); + } + long value = Long.parseLong(text); + if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) { + return Integer.valueOf((int) value); + } + return Long.valueOf(value); + } catch (IllegalArgumentException iae) { + throw ctxt.weirdStringException(_valueClass, "not a valid number"); + } + } + // Otherwise, no can do: + throw ctxt.mappingException(_valueClass, t); + } + + /** + * As mentioned in class Javadoc, there is additional complexity in + * handling potentially mixed type information here. Because of this, + * we must actually check for "raw" integers and doubles first, before + * calling type deserializer. + */ + @Override + public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt, + TypeDeserializer typeDeserializer) + throws IOException, JsonProcessingException + { + switch (jp.getCurrentToken()) { + case VALUE_NUMBER_INT: + case VALUE_NUMBER_FLOAT: + case VALUE_STRING: + // can not point to type information: hence must be non-typed (int/double) + return deserialize(jp, ctxt); + } + return typeDeserializer.deserializeTypedFromScalar(jp, ctxt); + } + } + + /* + /********************************************************** + /* And then bit more complicated (but non-structured) number + /* types + /********************************************************** + */ + + /** + * This is bit trickier to implement efficiently, while avoiding + * overflow problems. + */ + @JacksonStdImpl + public static class BigIntegerDeserializer + extends StdScalarDeserializer + { + public BigIntegerDeserializer() { super(BigInteger.class); } + + @Override + public BigInteger deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException + { + JsonToken t = jp.getCurrentToken(); + String text; + + if (t == JsonToken.VALUE_NUMBER_INT) { + switch (jp.getNumberType()) { + case INT: + case LONG: + return BigInteger.valueOf(jp.getLongValue()); + } + } else if (t == JsonToken.VALUE_NUMBER_FLOAT) { + /* Whether to fail if there's non-integer part? + * Could do by calling BigDecimal.toBigIntegerExact() + */ + return jp.getDecimalValue().toBigInteger(); + } else if (t != JsonToken.VALUE_STRING) { // let's do implicit re-parse + // String is ok too, can easily convert; otherwise, no can do: + throw ctxt.mappingException(_valueClass, t); + } + text = jp.getText().trim(); + if (text.length() == 0) { + return null; + } + try { + return new BigInteger(text); + } catch (IllegalArgumentException iae) { + throw ctxt.weirdStringException(_valueClass, "not a valid representation"); + } + } + } + + @JacksonStdImpl + public static class BigDecimalDeserializer + extends StdScalarDeserializer + { + public BigDecimalDeserializer() { super(BigDecimal.class); } + + @Override + public BigDecimal deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException + { + JsonToken t = jp.getCurrentToken(); + if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) { + return jp.getDecimalValue(); + } + // String is ok too, can easily convert + if (t == JsonToken.VALUE_STRING) { // let's do implicit re-parse + String text = jp.getText().trim(); + if (text.length() == 0) { + return null; + } + try { + return new BigDecimal(text); + } catch (IllegalArgumentException iae) { + throw ctxt.weirdStringException(_valueClass, "not a valid representation"); + } + } + // Otherwise, no can do: + throw ctxt.mappingException(_valueClass, t); + } + } + + +} diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java index baf56caa1..76a7152c6 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java @@ -1,8 +1,6 @@ package com.fasterxml.jackson.databind.deser.std; import java.io.IOException; -import java.math.BigDecimal; -import java.math.BigInteger; import java.util.*; import com.fasterxml.jackson.core.*; @@ -56,8 +54,6 @@ public abstract class StdDeserializer * deserializer Jackson uses; as opposed to a custom deserializer installed by * a module or calling application. Determination is done using * {@link JacksonStdImpl} annotation on deserializer class. - * - * @since 1.7 */ protected boolean isDefaultSerializer(JsonDeserializer deserializer) { @@ -85,7 +81,9 @@ public abstract class StdDeserializer /* /********************************************************** - /* Helper methods for sub-classes, parsing + /* Helper methods for sub-classes, parsing: while mostly + /* useful for numeric types, can be also useful for dealing + /* with things serialized as numbers (such as Dates). /********************************************************** */ @@ -570,7 +568,7 @@ public abstract class StdDeserializer /**************************************************** /* Helper methods for sub-classes, resolving dependencies /**************************************************** - */ + */ /** * Helper method used to locate deserializers for properties the @@ -644,464 +642,4 @@ public abstract class StdDeserializer } // ... or if not, just ignore } - - - /* - /********************************************************** - /* Then one intermediate base class for things that have - /* both primitive and wrapper types - /********************************************************** - */ - - protected abstract static class PrimitiveOrWrapperDeserializer - extends StdScalarDeserializer - { - final T _nullValue; - - protected PrimitiveOrWrapperDeserializer(Class vc, T nvl) - { - super(vc); - _nullValue = nvl; - } - - @Override - public final T getNullValue() { - return _nullValue; - } - } - - /* - /********************************************************** - /* Then primitive/wrapper types - /********************************************************** - */ - - @JacksonStdImpl - public final static class BooleanDeserializer - extends PrimitiveOrWrapperDeserializer - { - public BooleanDeserializer(Class cls, Boolean nvl) - { - super(cls, nvl); - } - - @Override - public Boolean deserialize(JsonParser jp, DeserializationContext ctxt) - throws IOException, JsonProcessingException - { - return _parseBoolean(jp, ctxt); - } - - // 1.6: since we can never have type info ("natural type"; String, Boolean, Integer, Double): - // (is it an error to even call this version?) - @Override - public Boolean deserializeWithType(JsonParser jp, DeserializationContext ctxt, - TypeDeserializer typeDeserializer) - throws IOException, JsonProcessingException - { - return _parseBoolean(jp, ctxt); - } - } - - @JacksonStdImpl - public final static class ByteDeserializer - extends PrimitiveOrWrapperDeserializer - { - public ByteDeserializer(Class cls, Byte nvl) - { - super(cls, nvl); - } - - @Override - public Byte deserialize(JsonParser jp, DeserializationContext ctxt) - throws IOException, JsonProcessingException - { - return _parseByte(jp, ctxt); - } - } - - @JacksonStdImpl - public final static class ShortDeserializer - extends PrimitiveOrWrapperDeserializer - { - public ShortDeserializer(Class cls, Short nvl) - { - super(cls, nvl); - } - - @Override - public Short deserialize(JsonParser jp, DeserializationContext ctxt) - throws IOException, JsonProcessingException - { - return _parseShort(jp, ctxt); - } - } - - @JacksonStdImpl - public final static class CharacterDeserializer - extends PrimitiveOrWrapperDeserializer - { - public CharacterDeserializer(Class cls, Character nvl) - { - super(cls, nvl); - } - - @Override - public Character deserialize(JsonParser jp, DeserializationContext ctxt) - throws IOException, JsonProcessingException - { - JsonToken t = jp.getCurrentToken(); - int value; - - if (t == JsonToken.VALUE_NUMBER_INT) { // ok iff ascii value - value = jp.getIntValue(); - if (value >= 0 && value <= 0xFFFF) { - return Character.valueOf((char) value); - } - } else if (t == JsonToken.VALUE_STRING) { // this is the usual type - // But does it have to be exactly one char? - String text = jp.getText(); - if (text.length() == 1) { - return Character.valueOf(text.charAt(0)); - } - // actually, empty should become null? - if (text.length() == 0) { - return (Character) getEmptyValue(); - } - } - throw ctxt.mappingException(_valueClass, t); - } - } - - @JacksonStdImpl - public final static class IntegerDeserializer - extends PrimitiveOrWrapperDeserializer - { - public IntegerDeserializer(Class cls, Integer nvl) - { - super(cls, nvl); - } - - @Override - public Integer deserialize(JsonParser jp, DeserializationContext ctxt) - throws IOException, JsonProcessingException - { - return _parseInteger(jp, ctxt); - } - - // 1.6: since we can never have type info ("natural type"; String, Boolean, Integer, Double): - // (is it an error to even call this version?) - @Override - public Integer deserializeWithType(JsonParser jp, DeserializationContext ctxt, - TypeDeserializer typeDeserializer) - throws IOException, JsonProcessingException - { - return _parseInteger(jp, ctxt); - } - } - - @JacksonStdImpl - public final static class LongDeserializer - extends PrimitiveOrWrapperDeserializer - { - public LongDeserializer(Class cls, Long nvl) - { - super(cls, nvl); - } - - @Override - public Long deserialize(JsonParser jp, DeserializationContext ctxt) - throws IOException, JsonProcessingException - { - return _parseLong(jp, ctxt); - } - } - - @JacksonStdImpl - public final static class FloatDeserializer - extends PrimitiveOrWrapperDeserializer - { - public FloatDeserializer(Class cls, Float nvl) - { - super(cls, nvl); - } - - @Override - public Float deserialize(JsonParser jp, DeserializationContext ctxt) - throws IOException, JsonProcessingException - { - /* 22-Jan-2009, tatu: Bounds/range checks would be tricky - * here, so let's not bother even trying... - */ - return _parseFloat(jp, ctxt); - } - } - - @JacksonStdImpl - public final static class DoubleDeserializer - extends PrimitiveOrWrapperDeserializer - { - public DoubleDeserializer(Class cls, Double nvl) - { - super(cls, nvl); - } - - @Override - public Double deserialize(JsonParser jp, DeserializationContext ctxt) - throws IOException, JsonProcessingException - { - return _parseDouble(jp, ctxt); - } - - // 1.6: since we can never have type info ("natural type"; String, Boolean, Integer, Double): - // (is it an error to even call this version?) - @Override - public Double deserializeWithType(JsonParser jp, DeserializationContext ctxt, - TypeDeserializer typeDeserializer) - throws IOException, JsonProcessingException - { - return _parseDouble(jp, ctxt); - } - } - - /** - * For type Number.class, we can just rely on type - * mappings that plain {@link JsonParser#getNumberValue} returns. - *

- * Since 1.5, there is one additional complication: some numeric - * types (specifically, int/Integer and double/Double) are "non-typed"; - * meaning that they will NEVER be output with type information. - * But other numeric types may need such type information. - * This is why {@link #deserializeWithType} must be overridden. - */ - @JacksonStdImpl - public final static class NumberDeserializer - extends StdScalarDeserializer - { - public NumberDeserializer() { super(Number.class); } - - @Override - public Number deserialize(JsonParser jp, DeserializationContext ctxt) - throws IOException, JsonProcessingException - { - JsonToken t = jp.getCurrentToken(); - if (t == JsonToken.VALUE_NUMBER_INT) { - if (ctxt.isEnabled(DeserializationConfig.Feature.USE_BIG_INTEGER_FOR_INTS)) { - return jp.getBigIntegerValue(); - } - return jp.getNumberValue(); - } else if (t == JsonToken.VALUE_NUMBER_FLOAT) { - /* [JACKSON-72]: need to allow overriding the behavior - * regarding which type to use - */ - if (ctxt.isEnabled(DeserializationConfig.Feature.USE_BIG_DECIMAL_FOR_FLOATS)) { - return jp.getDecimalValue(); - } - return Double.valueOf(jp.getDoubleValue()); - } - - /* Textual values are more difficult... not parsing itself, but figuring - * out 'minimal' type to use - */ - if (t == JsonToken.VALUE_STRING) { // let's do implicit re-parse - String text = jp.getText().trim(); - try { - if (text.indexOf('.') >= 0) { // floating point - // as per [JACKSON-72]: - if (ctxt.isEnabled(DeserializationConfig.Feature.USE_BIG_DECIMAL_FOR_FLOATS)) { - return new BigDecimal(text); - } - return new Double(text); - } - // as per [JACKSON-100]: - if (ctxt.isEnabled(DeserializationConfig.Feature.USE_BIG_INTEGER_FOR_INTS)) { - return new BigInteger(text); - } - long value = Long.parseLong(text); - if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) { - return Integer.valueOf((int) value); - } - return Long.valueOf(value); - } catch (IllegalArgumentException iae) { - throw ctxt.weirdStringException(_valueClass, "not a valid number"); - } - } - // Otherwise, no can do: - throw ctxt.mappingException(_valueClass, t); - } - - /** - * As mentioned in class Javadoc, there is additional complexity in - * handling potentially mixed type information here. Because of this, - * we must actually check for "raw" integers and doubles first, before - * calling type deserializer. - */ - @Override - public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt, - TypeDeserializer typeDeserializer) - throws IOException, JsonProcessingException - { - switch (jp.getCurrentToken()) { - case VALUE_NUMBER_INT: - case VALUE_NUMBER_FLOAT: - case VALUE_STRING: - // can not point to type information: hence must be non-typed (int/double) - return deserialize(jp, ctxt); - } - return typeDeserializer.deserializeTypedFromScalar(jp, ctxt); - } - } - - /* - /********************************************************** - /* And then bit more complicated (but non-structured) number - /* types - /********************************************************** - */ - - @JacksonStdImpl - public static class BigDecimalDeserializer - extends StdScalarDeserializer - { - public BigDecimalDeserializer() { super(BigDecimal.class); } - - @Override - public BigDecimal deserialize(JsonParser jp, DeserializationContext ctxt) - throws IOException, JsonProcessingException - { - JsonToken t = jp.getCurrentToken(); - if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) { - return jp.getDecimalValue(); - } - // String is ok too, can easily convert - if (t == JsonToken.VALUE_STRING) { // let's do implicit re-parse - String text = jp.getText().trim(); - if (text.length() == 0) { - return null; - } - try { - return new BigDecimal(text); - } catch (IllegalArgumentException iae) { - throw ctxt.weirdStringException(_valueClass, "not a valid representation"); - } - } - // Otherwise, no can do: - throw ctxt.mappingException(_valueClass, t); - } - } - - /** - * This is bit trickier to implement efficiently, while avoiding - * overflow problems. - */ - @JacksonStdImpl - public static class BigIntegerDeserializer - extends StdScalarDeserializer - { - public BigIntegerDeserializer() { super(BigInteger.class); } - - @Override - public BigInteger deserialize(JsonParser jp, DeserializationContext ctxt) - throws IOException, JsonProcessingException - { - JsonToken t = jp.getCurrentToken(); - String text; - - if (t == JsonToken.VALUE_NUMBER_INT) { - switch (jp.getNumberType()) { - case INT: - case LONG: - return BigInteger.valueOf(jp.getLongValue()); - } - } else if (t == JsonToken.VALUE_NUMBER_FLOAT) { - /* Whether to fail if there's non-integer part? - * Could do by calling BigDecimal.toBigIntegerExact() - */ - return jp.getDecimalValue().toBigInteger(); - } else if (t != JsonToken.VALUE_STRING) { // let's do implicit re-parse - // String is ok too, can easily convert; otherwise, no can do: - throw ctxt.mappingException(_valueClass, t); - } - text = jp.getText().trim(); - if (text.length() == 0) { - return null; - } - try { - return new BigInteger(text); - } catch (IllegalArgumentException iae) { - throw ctxt.weirdStringException(_valueClass, "not a valid representation"); - } - } - } - - /* - /**************************************************** - /* Then trickier things: Date/Calendar types - /**************************************************** - */ - - /** - * Compared to plain old {@link java.util.Date}, SQL version is easier - * to deal with: mostly because it is more limited. - */ - public static class SqlDateDeserializer - extends StdScalarDeserializer - { - public SqlDateDeserializer() { super(java.sql.Date.class); } - - @Override - public java.sql.Date deserialize(JsonParser jp, DeserializationContext ctxt) - throws IOException, JsonProcessingException - { - Date d = _parseDate(jp, ctxt); - return (d == null) ? null : new java.sql.Date(d.getTime()); - } - } - - /* - /**************************************************** - /* And other oddities - /**************************************************** - */ - - public static class StackTraceElementDeserializer - extends StdScalarDeserializer - { - public StackTraceElementDeserializer() { super(StackTraceElement.class); } - - @Override - public StackTraceElement deserialize(JsonParser jp, DeserializationContext ctxt) - throws IOException, JsonProcessingException - { - JsonToken t = jp.getCurrentToken(); - // Must get an Object - if (t == JsonToken.START_OBJECT) { - String className = "", methodName = "", fileName = ""; - int lineNumber = -1; - - while ((t = jp.nextValue()) != JsonToken.END_OBJECT) { - String propName = jp.getCurrentName(); - if ("className".equals(propName)) { - className = jp.getText(); - } else if ("fileName".equals(propName)) { - fileName = jp.getText(); - } else if ("lineNumber".equals(propName)) { - if (t.isNumeric()) { - lineNumber = jp.getIntValue(); - } else { - throw JsonMappingException.from(jp, "Non-numeric token ("+t+") for property 'lineNumber'"); - } - } else if ("methodName".equals(propName)) { - methodName = jp.getText(); - } else if ("nativeMethod".equals(propName)) { - // no setter, not passed via constructor: ignore - } else { - handleUnknownProperty(jp, ctxt, _valueClass, propName); - } - } - return new StackTraceElement(className, methodName, fileName, lineNumber); - } - throw ctxt.mappingException(_valueClass, t); - } - } } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StringCollectionDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StringCollectionDeserializer.java index 52594d008..e3b3396ae 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StringCollectionDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StringCollectionDeserializer.java @@ -11,7 +11,11 @@ import com.fasterxml.jackson.databind.deser.ValueInstantiator; import com.fasterxml.jackson.databind.introspect.AnnotatedWithParams; import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; - +/** + * Specifically optimized version for {@link java.util.Collection}s + * that contain String values; reason is that this is a very common + * type and we can make use of the fact that Strings are final. + */ @JacksonStdImpl public final class StringCollectionDeserializer extends ContainerDeserializerBase> @@ -37,7 +41,7 @@ public final class StringCollectionDeserializer // // Instance construction settings: /** - * @since 1.9 + * Instantiator used in case custom handling is needed for creation. */ protected final ValueInstantiator _valueInstantiator; @@ -69,8 +73,6 @@ public final class StringCollectionDeserializer /** * Copy-constructor that can be used by sub-classes to allow * copy-on-write styling copying of settings of an existing instance. - * - * @since 1.9 */ protected StringCollectionDeserializer(StringCollectionDeserializer src) { diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StringDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StringDeserializer.java index 458299e2a..1c685e2f5 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StringDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StringDeserializer.java @@ -50,4 +50,5 @@ public class StringDeserializer throws IOException, JsonProcessingException { return deserialize(jp, ctxt); - }} + } +} diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/TimestampDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/TimestampDeserializer.java deleted file mode 100644 index 46ee0f51c..000000000 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/TimestampDeserializer.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.fasterxml.jackson.databind.deser.std; - -import java.io.IOException; -import java.sql.Timestamp; - -import com.fasterxml.jackson.core.*; - -import com.fasterxml.jackson.databind.DeserializationContext; - -/** - * Simple deserializer for handling {@link java.sql.Timestamp} values. - *

- * One way to customize Timestamp formats accepted is to override method - * {@link DeserializationContext#parseDate} that this basic - * deserializer calls. - */ -public class TimestampDeserializer - extends StdScalarDeserializer -{ - public TimestampDeserializer() { super(Timestamp.class); } - - @Override - public java.sql.Timestamp deserialize(JsonParser jp, DeserializationContext ctxt) - throws IOException, JsonProcessingException - { - return new Timestamp(_parseDate(jp, ctxt).getTime()); - } -} diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/TokenBufferDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/TokenBufferDeserializer.java deleted file mode 100644 index cf3bd7b39..000000000 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/TokenBufferDeserializer.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.fasterxml.jackson.databind.deser.std; - -import java.io.IOException; - -import com.fasterxml.jackson.core.*; - -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; -import com.fasterxml.jackson.databind.util.TokenBuffer; - -/** - * We also want to directly support deserialization of - * {@link TokenBuffer}. - *

- * Note that we use scalar deserializer base just because we claim - * to be of scalar for type information inclusion purposes; actual - * underlying content can be of any (Object, Array, scalar) type. - * - * @since 1.9 (moved from higher-level package) - */ -@JacksonStdImpl -public class TokenBufferDeserializer - extends StdScalarDeserializer -{ - public TokenBufferDeserializer() { super(TokenBuffer.class); } - - @Override - public TokenBuffer deserialize(JsonParser jp, DeserializationContext ctxt) - throws IOException, JsonProcessingException - { - TokenBuffer tb = new TokenBuffer(jp.getCodec()); - // quite simple, given that TokenBuffer is a JsonGenerator: - tb.copyCurrentStructure(jp); - return tb; - } -} diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestAnyProperties.java b/src/test/java/com/fasterxml/jackson/databind/deser/TestAnyProperties.java index 443a4ef78..08aa5d4f9 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/TestAnyProperties.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/TestAnyProperties.java @@ -77,6 +77,29 @@ public class TestAnyProperties } } + static class Bean744 + { + protected Map additionalProperties; + + @JsonAnySetter + public void addAdditionalProperty(String key, Object value) { + if (additionalProperties == null) additionalProperties = new HashMap(); + additionalProperties.put(key,value); + } + + public void setAdditionalProperties(Map additionalProperties) { + this.additionalProperties = additionalProperties; + } + + @JsonAnyGetter + public Map getAdditionalProperties() { return additionalProperties; } + + @JsonIgnore + public String getName() { + return (String) additionalProperties.get("name"); + } + } + /* /********************************************************** /* Test methods @@ -139,4 +162,14 @@ public class TestAnyProperties assertEquals("Bob", bean.map.get("name")); assertEquals(1, bean.map.size()); } + + public void testProblem744() throws Exception + { + ObjectMapper m = new ObjectMapper(); + Bean744 bean = m.readValue("{\"name\":\"Bob\"}", Bean744.class); + assertNotNull(bean.additionalProperties); + assertEquals(1, bean.additionalProperties.size()); + assertEquals("Bob", bean.additionalProperties.get("name")); + } + } diff --git a/src/test/java/com/fasterxml/jackson/databind/introspect/TestPOJOPropertiesCollector.java b/src/test/java/com/fasterxml/jackson/databind/introspect/TestPOJOPropertiesCollector.java index d6262f809..427829e7e 100644 --- a/src/test/java/com/fasterxml/jackson/databind/introspect/TestPOJOPropertiesCollector.java +++ b/src/test/java/com/fasterxml/jackson/databind/introspect/TestPOJOPropertiesCollector.java @@ -3,6 +3,7 @@ package com.fasterxml.jackson.databind.introspect; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -164,6 +165,29 @@ public class TestPOJOPropertiesCollector public int getX() { return i; } } + + static class Issue744Bean + { + protected Map additionalProperties; + + @JsonAnySetter + public void addAdditionalProperty(String key, Object value) { + if (additionalProperties == null) additionalProperties = new HashMap(); + additionalProperties.put(key,value); + } + + public void setAdditionalProperties(Map additionalProperties) { + this.additionalProperties = additionalProperties; + } + + @JsonAnyGetter + public Map getAdditionalProperties() { return additionalProperties; } + + @JsonIgnore + public String getName() { + return (String) additionalProperties.get("name"); + } + } /* /********************************************************** @@ -362,6 +386,15 @@ public class TestPOJOPropertiesCollector String json = mapper.writeValueAsString(bean); assertNotNull(json); } + + public void testJackson744() throws Exception + { + ObjectMapper mapper = new ObjectMapper(); + BasicBeanDescription beanDesc = mapper.getDeserializationConfig().introspect(mapper.constructType(Issue744Bean.class)); + assertNotNull(beanDesc); + AnnotatedMethod setter = beanDesc.findAnySetter(); + assertNotNull(setter); + } /* /********************************************************** -- cgit v1.2.3