Skip to content

Commit

Permalink
Fix #504 and #797
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed May 20, 2015
1 parent 48a21be commit 23e52ab
Show file tree
Hide file tree
Showing 13 changed files with 198 additions and 60 deletions.
6 changes: 6 additions & 0 deletions release-notes/CREDITS
Expand Up @@ -271,3 +271,9 @@ Michal Letynski (mletynski@github)
* Suggested #296: Serialization of transient fields with public getters (add * Suggested #296: Serialization of transient fields with public getters (add
MapperFeature.PROPAGATE_TRANSIENT_MARKER) MapperFeature.PROPAGATE_TRANSIENT_MARKER)
(2.6.0) (2.6.0)

Jeff Schnitzer (stickfigure@github)
* Suggested #504: Add `DeserializationFeature.USE_LONG_FOR_INTS`
(2.6.0)


3 changes: 3 additions & 0 deletions release-notes/VERSION
Expand Up @@ -14,6 +14,8 @@ Project: jackson-databind
#312: Support Type Id mappings where two ids map to same Class #312: Support Type Id mappings where two ids map to same Class
#348: ObjectMapper.valueToTree does not work with @JsonRawValue #348: ObjectMapper.valueToTree does not work with @JsonRawValue
(reported by Chris P, pimlottc@github) (reported by Chris P, pimlottc@github)
#504: Add `DeserializationFeature.USE_LONG_FOR_INTS`
(suggested by Jeff S)
#649: Make `BeanDeserializer` use new `parser.nextFieldName()` and `.hasTokenId()` methods #649: Make `BeanDeserializer` use new `parser.nextFieldName()` and `.hasTokenId()` methods
#664: Add `DeserializationFeature.ACCEPT_FLOAT_AS_INT` to prevent coercion of floating point #664: Add `DeserializationFeature.ACCEPT_FLOAT_AS_INT` to prevent coercion of floating point
numbers int `int`/`long`/`Integer`/`Long` numbers int `int`/`long`/`Integer`/`Long`
Expand Down Expand Up @@ -51,6 +53,7 @@ Project: jackson-databind
timezone id for date/time values (as opposed to timezone offset) timezone id for date/time values (as opposed to timezone offset)
#795: Converter annotation not honored for abstract types #795: Converter annotation not honored for abstract types
(reported by myrosia@github) (reported by myrosia@github)
#797: `JsonNodeFactory` method `numberNode(long)` produces `IntNode` for small numbers
- Remove old cglib compatibility tests; cause problems in Eclipse - Remove old cglib compatibility tests; cause problems in Eclipse


2.5.4 (not yet released) 2.5.4 (not yet released)
Expand Down
Expand Up @@ -648,7 +648,7 @@ public final boolean isEnabled(JsonParser.Feature f, JsonFactory factory) {
} }


/** /**
* "Bulk" access method for checking that all features specified by * Bulk access method for checking that all features specified by
* mask are enabled. * mask are enabled.
* *
* @since 2.3 * @since 2.3
Expand All @@ -657,6 +657,20 @@ public final boolean hasDeserializationFeatures(int featureMask) {
return (_deserFeatures & featureMask) == featureMask; return (_deserFeatures & featureMask) == featureMask;
} }


/**
* Bulk access method for checking that at least one of features specified by
* mask is enabled.
*
* @since 2.6
*/
public final boolean hasSomeOfFeatures(int featureMask) {
return (_deserFeatures & featureMask) != 0;
}

/**
* Bulk access method for getting the bit mask of all {@link DeserializationFeature}s
* that are enabled.
*/
public final int getDeserializationFeatures() { public final int getDeserializationFeatures() {
return _deserFeatures; return _deserFeatures;
} }
Expand Down
Expand Up @@ -291,13 +291,33 @@ public final boolean isEnabled(DeserializationFeature feat) {
} }


/** /**
* "Bulk" access method for checking that all features specified by * Bulk access method for getting the bit mask of all {@link DeserializationFeature}s
* that are enabled.
*
* @since 2.6
*/
public final int getDeserializationFeatures() {
return _featureFlags;
}

/**
* Bulk access method for checking that all features specified by
* mask are enabled. * mask are enabled.
* *
* @since 2.3 * @since 2.3
*/ */
public final boolean hasDeserializationFeatures(int featureMask) { public final boolean hasDeserializationFeatures(int featureMask) {
return _config.hasDeserializationFeatures(featureMask); return (_featureFlags & featureMask) == featureMask;
}

/**
* Bulk access method for checking that at least one of features specified by
* mask is enabled.
*
* @since 2.6
*/
public final boolean hasSomeOfFeatures(int featureMask) {
return (_featureFlags & featureMask) != 0;
} }


/** /**
Expand Down
Expand Up @@ -53,13 +53,35 @@ public enum DeserializationFeature implements ConfigFeature
* which is either {@link Integer}, {@link Long} or * which is either {@link Integer}, {@link Long} or
* {@link java.math.BigInteger}, depending on number of digits. * {@link java.math.BigInteger}, depending on number of digits.
* <p> * <p>
* Feature is disabled by default, meaning that "untyped" floating * Feature is disabled by default, meaning that "untyped" integral
* point numbers will by default be deserialized using whatever * numbers will by default be deserialized using whatever
* is the most compact integral type, to optimize efficiency. * is the most compact integral type, to optimize efficiency.
*/ */
USE_BIG_INTEGER_FOR_INTS(false), USE_BIG_INTEGER_FOR_INTS(false),


// [JACKSON-652] /**
* Feature that determines how "small" JSON integral (non-floating-point)
* numbers -- ones that fit in 32-bit signed integer (`int`) -- are bound
* when target type is loosely typed as {@link Object} or {@link Number}
* (or within untyped {@link java.util.Map} or {@link java.util.Collection} context).
* If enabled, such values will be deserialized as {@link java.lang.Long};
* if disabled, they will be deserialized as "smallest" available type,
* {@link Integer}.
* In addition, if enabled, trying to bind values that do not fit in {@link java.lang.Long}
* will throw a {@link com.fasterxml.jackson.core.JsonProcessingException}.
*<p>
* Note: if {@link #USE_BIG_INTEGER_FOR_INTS} is enabled, it has precedence
* over this setting, forcing use of {@link java.math.BigInteger} for all
* integral values.
*<p>
* Feature is disabled by default, meaning that "untyped" integral
* numbers will by default be deserialized using {@link java.lang.Integer}
* if value fits.
*
* @since 2.6
*/
USE_LONG_FOR_INTS(false),

/** /**
* Feature that determines whether JSON Array is mapped to * Feature that determines whether JSON Array is mapped to
* <code>Object[]</code> or <code>List&lt;Object></code> when binding * <code>Object[]</code> or <code>List&lt;Object></code> when binding
Expand Down Expand Up @@ -402,4 +424,4 @@ private DeserializationFeature(boolean defaultState) {
* @since 2.5 * @since 2.5
*/ */
public boolean enabledIn(int flags) { return (flags & _mask) != 0; } public boolean enabledIn(int flags) { return (flags & _mask) != 0; }
} }
Expand Up @@ -1271,7 +1271,7 @@ public ObjectMapper setSerializationInclusion(JsonInclude.Include incl) {
* Method for specifying {@link PrettyPrinter} to use when "default pretty-printing" * Method for specifying {@link PrettyPrinter} to use when "default pretty-printing"
* is enabled (by enabling {@link SerializationFeature#INDENT_OUTPUT}) * is enabled (by enabling {@link SerializationFeature#INDENT_OUTPUT})
* *
* @param pp * @param pp Pretty printer to use by default.
* *
* @return This mapper, useful for call-chaining * @return This mapper, useful for call-chaining
* *
Expand Down
Expand Up @@ -331,15 +331,26 @@ protected final JsonNode deserializeAny(JsonParser p, DeserializationContext ctx
protected final JsonNode _fromInt(JsonParser p, DeserializationContext ctxt, protected final JsonNode _fromInt(JsonParser p, DeserializationContext ctxt,
JsonNodeFactory nodeFactory) throws IOException JsonNodeFactory nodeFactory) throws IOException
{ {
JsonParser.NumberType nt = p.getNumberType(); JsonParser.NumberType nt;
if (nt == JsonParser.NumberType.BIG_INTEGER int feats = ctxt.getDeserializationFeatures();
|| ctxt.isEnabled(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS)) { if ((feats & F_MASK_INT_COERCIONS) != 0) {
return nodeFactory.numberNode(p.getBigIntegerValue()); if (DeserializationFeature.USE_BIG_INTEGER_FOR_INTS.enabledIn(feats)) {
nt = JsonParser.NumberType.BIG_INTEGER;
} else if (DeserializationFeature.USE_LONG_FOR_INTS.enabledIn(feats)) {
nt = JsonParser.NumberType.LONG;
} else {
nt = p.getNumberType();
}
} else {
nt = p.getNumberType();
} }
if (nt == JsonParser.NumberType.INT) { if (nt == JsonParser.NumberType.INT) {
return nodeFactory.numberNode(p.getIntValue()); return nodeFactory.numberNode(p.getIntValue());
} }
return nodeFactory.numberNode(p.getLongValue()); if (nt == JsonParser.NumberType.LONG) {
return nodeFactory.numberNode(p.getLongValue());
}
return nodeFactory.numberNode(p.getBigIntegerValue());
} }


protected final JsonNode _fromFloat(JsonParser p, DeserializationContext ctxt, protected final JsonNode _fromFloat(JsonParser p, DeserializationContext ctxt,
Expand Down
Expand Up @@ -410,21 +410,24 @@ public Double deserializeWithType(JsonParser jp, DeserializationContext ctxt,
@SuppressWarnings("serial") @SuppressWarnings("serial")
@JacksonStdImpl @JacksonStdImpl
public static class NumberDeserializer public static class NumberDeserializer
extends StdScalarDeserializer<Number> extends StdScalarDeserializer<Object>
{ {
public final static NumberDeserializer instance = new NumberDeserializer(); public final static NumberDeserializer instance = new NumberDeserializer();


public NumberDeserializer() { super(Number.class); } public NumberDeserializer() {
super(Number.class);
}


@Override @Override
public Number deserialize(JsonParser p, DeserializationContext ctxt) throws IOException public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{ {
switch (p.getCurrentTokenId()) { switch (p.getCurrentTokenId()) {
case JsonTokenId.ID_NUMBER_INT: case JsonTokenId.ID_NUMBER_INT:
if (ctxt.isEnabled(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS)) { if (ctxt.hasSomeOfFeatures(F_MASK_INT_COERCIONS)) {
return p.getBigIntegerValue(); return _coerceIntegral(p, ctxt);
} }
return p.getNumberValue(); return p.getNumberValue();

case JsonTokenId.ID_NUMBER_FLOAT: case JsonTokenId.ID_NUMBER_FLOAT:
if (ctxt.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)) { if (ctxt.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)) {
return p.getDecimalValue(); return p.getDecimalValue();
Expand Down Expand Up @@ -462,8 +465,10 @@ public Number deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx
return new BigInteger(text); return new BigInteger(text);
} }
long value = Long.parseLong(text); long value = Long.parseLong(text);
if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) { if (!ctxt.isEnabled(DeserializationFeature.USE_LONG_FOR_INTS)) {
return Integer.valueOf((int) value); if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {
return Integer.valueOf((int) value);
}
} }
return Long.valueOf(value); return Long.valueOf(value);
} catch (IllegalArgumentException iae) { } catch (IllegalArgumentException iae) {
Expand All @@ -472,7 +477,7 @@ public Number deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx
case JsonTokenId.ID_START_ARRAY: case JsonTokenId.ID_START_ARRAY:
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
p.nextToken(); p.nextToken();
final Number value = deserialize(p, ctxt); final Object value = deserialize(p, ctxt);
if (p.nextToken() != JsonToken.END_ARRAY) { if (p.nextToken() != JsonToken.END_ARRAY) {
throw ctxt.wrongTokenException(p, JsonToken.END_ARRAY, throw ctxt.wrongTokenException(p, JsonToken.END_ARRAY,
"Attempted to unwrap single value array for single '" + _valueClass.getName() + "' value but there was more than a single value in the array" "Attempted to unwrap single value array for single '" + _valueClass.getName() + "' value but there was more than a single value in the array"
Expand Down
Expand Up @@ -24,6 +24,17 @@ public abstract class StdDeserializer<T>
{ {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;


/**
* Bitmask that covers {@link DeserializationFeature#USE_BIG_INTEGER_FOR_INTS}
* and {@link DeserializationFeature#USE_LONG_FOR_INTS}, used for more efficient
* cheks when coercing integral values for untyped deserialization.
*
* @since 2.6
*/
protected final static int F_MASK_INT_COERCIONS =
DeserializationFeature.USE_BIG_INTEGER_FOR_INTS.getMask()
| DeserializationFeature.USE_LONG_FOR_INTS.getMask();

/** /**
* Type of values this deserializer handles: sometimes * Type of values this deserializer handles: sometimes
* exact types, other time most specific supertype of * exact types, other time most specific supertype of
Expand Down Expand Up @@ -884,6 +895,34 @@ protected final boolean _isPosInf(String text) {
} }


protected final boolean _isNaN(String text) { return "NaN".equals(text); } protected final boolean _isNaN(String text) { return "NaN".equals(text); }

/*
/****************************************************
/* Helper methods for sub-classes, coercions
/****************************************************
*/

/**
* Helper method called in case where an integral number is encountered, but
* config settings suggest that a coercion may be needed to "upgrade"
* {@link java.lang.Number} into "bigger" type like {@link java.lang.Long} or
* {@link java.math.BigInteger}
*
* @see {@link DeserializationFeature#USE_BIG_INTEGER_FOR_INTS}, {@link DeserializationFeature#USE_LONG_FOR_INTS}
*
* @since 2.6
*/
protected Object _coerceIntegral(JsonParser p, DeserializationContext ctxt) throws IOException
{
int feats = ctxt.getDeserializationFeatures();
if (DeserializationFeature.USE_BIG_INTEGER_FOR_INTS.enabledIn(feats)) {
return p.getBigIntegerValue();
}
if (DeserializationFeature.USE_LONG_FOR_INTS.enabledIn(feats)) {
return p.getLongValue();
}
return p.getBigIntegerValue(); // should be optimal, whatever it is
}


/* /*
/**************************************************** /****************************************************
Expand Down
Expand Up @@ -240,11 +240,11 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx
if (_numberDeserializer != null) { if (_numberDeserializer != null) {
return _numberDeserializer.deserialize(p, ctxt); return _numberDeserializer.deserialize(p, ctxt);
} }
/* [JACKSON-100]: caller may want to get all integral values /* Caller may want to get all integral values returned as {@link java.math.BigInteger},
* returned as BigInteger, for consistency * or {@link java.lang.Long} for consistency
*/ */
if (ctxt.isEnabled(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS)) { if (ctxt.hasSomeOfFeatures(F_MASK_INT_COERCIONS)) {
return p.getBigIntegerValue(); // should be optimal, whatever it is return _coerceIntegral(p, ctxt);
} }
return p.getNumberValue(); // should be optimal, whatever it is return p.getNumberValue(); // should be optimal, whatever it is


Expand Down Expand Up @@ -304,15 +304,11 @@ public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, Typ
if (_numberDeserializer != null) { if (_numberDeserializer != null) {
return _numberDeserializer.deserialize(p, ctxt); return _numberDeserializer.deserialize(p, ctxt);
} }
// For [JACKSON-100], see above: // May need coercion to "bigger" types:
if (ctxt.isEnabled(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS)) { if (ctxt.hasSomeOfFeatures(F_MASK_INT_COERCIONS)) {
return p.getBigIntegerValue(); return _coerceIntegral(p, ctxt);
} }
/* and as per [JACKSON-839], allow "upgrade" to bigger types: out-of-range return p.getNumberValue(); // should be optimal, whatever it is
* entries can not be produced without type, so this should "just work",
* even if it is bit unclean
*/
return p.getNumberValue();


case JsonTokenId.ID_NUMBER_FLOAT: case JsonTokenId.ID_NUMBER_FLOAT:
if (_numberDeserializer != null) { if (_numberDeserializer != null) {
Expand Down Expand Up @@ -484,23 +480,23 @@ public static class Vanilla
public final static Vanilla std = new Vanilla(); public final static Vanilla std = new Vanilla();


public Vanilla() { super(Object.class); } public Vanilla() { super(Object.class); }

@Override @Override
public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{ {
switch (jp.getCurrentTokenId()) { switch (p.getCurrentTokenId()) {
case JsonTokenId.ID_START_OBJECT: case JsonTokenId.ID_START_OBJECT:
{ {
JsonToken t = jp.nextToken(); JsonToken t = p.nextToken();
if (t == JsonToken.END_OBJECT) { if (t == JsonToken.END_OBJECT) {
return new LinkedHashMap<String,Object>(2); return new LinkedHashMap<String,Object>(2);
} }
} }
case JsonTokenId.ID_FIELD_NAME: case JsonTokenId.ID_FIELD_NAME:
return mapObject(jp, ctxt); return mapObject(p, ctxt);
case JsonTokenId.ID_START_ARRAY: case JsonTokenId.ID_START_ARRAY:
{ {
JsonToken t = jp.nextToken(); JsonToken t = p.nextToken();
if (t == JsonToken.END_ARRAY) { // and empty one too if (t == JsonToken.END_ARRAY) { // and empty one too
if (ctxt.isEnabled(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY)) { if (ctxt.isEnabled(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY)) {
return NO_OBJECTS; return NO_OBJECTS;
Expand All @@ -509,25 +505,25 @@ public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOE
} }
} }
if (ctxt.isEnabled(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY)) { if (ctxt.isEnabled(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY)) {
return mapArrayToArray(jp, ctxt); return mapArrayToArray(p, ctxt);
} }
return mapArray(jp, ctxt); return mapArray(p, ctxt);
case JsonTokenId.ID_EMBEDDED_OBJECT: case JsonTokenId.ID_EMBEDDED_OBJECT:
return jp.getEmbeddedObject(); return p.getEmbeddedObject();
case JsonTokenId.ID_STRING: case JsonTokenId.ID_STRING:
return jp.getText(); return p.getText();


case JsonTokenId.ID_NUMBER_INT: case JsonTokenId.ID_NUMBER_INT:
if (ctxt.isEnabled(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS)) { if (ctxt.hasSomeOfFeatures(F_MASK_INT_COERCIONS)) {
return jp.getBigIntegerValue(); // should be optimal, whatever it is return _coerceIntegral(p, ctxt);
} }
return jp.getNumberValue(); // should be optimal, whatever it is return p.getNumberValue(); // should be optimal, whatever it is


case JsonTokenId.ID_NUMBER_FLOAT: case JsonTokenId.ID_NUMBER_FLOAT:
if (ctxt.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)) { if (ctxt.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)) {
return jp.getDecimalValue(); return p.getDecimalValue();
} }
return Double.valueOf(jp.getDoubleValue()); return Double.valueOf(p.getDoubleValue());


case JsonTokenId.ID_TRUE: case JsonTokenId.ID_TRUE:
return Boolean.TRUE; return Boolean.TRUE;
Expand Down

0 comments on commit 23e52ab

Please sign in to comment.