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
Original file line number Diff line number Diff line change
Expand Up @@ -271,3 +271,9 @@ Michal Letynski (mletynski@github)
* Suggested #296: Serialization of transient fields with public getters (add
MapperFeature.PROPAGATE_TRANSIENT_MARKER)
(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
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Project: jackson-databind
#312: Support Type Id mappings where two ids map to same Class
#348: ObjectMapper.valueToTree does not work with @JsonRawValue
(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
#664: Add `DeserializationFeature.ACCEPT_FLOAT_AS_INT` to prevent coercion of floating point
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)
#795: Converter annotation not honored for abstract types
(reported by myrosia@github)
#797: `JsonNodeFactory` method `numberNode(long)` produces `IntNode` for small numbers
- Remove old cglib compatibility tests; cause problems in Eclipse

2.5.4 (not yet released)
Expand Down
Original file line number Diff line number Diff line change
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.
*
* @since 2.3
Expand All @@ -657,6 +657,20 @@ public final boolean hasDeserializationFeatures(int 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() {
return _deserFeatures;
}
Expand Down
Original file line number Diff line number Diff line change
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.
*
* @since 2.3
*/
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
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,35 @@ public enum DeserializationFeature implements ConfigFeature
* which is either {@link Integer}, {@link Long} or
* {@link java.math.BigInteger}, depending on number of digits.
* <p>
* Feature is disabled by default, meaning that "untyped" floating
* point numbers will by default be deserialized using whatever
* Feature is disabled by default, meaning that "untyped" integral
* numbers will by default be deserialized using whatever
* is the most compact integral type, to optimize efficiency.
*/
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
* <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
*/
public boolean enabledIn(int flags) { return (flags & _mask) != 0; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1271,7 +1271,7 @@ public ObjectMapper setSerializationInclusion(JsonInclude.Include incl) {
* Method for specifying {@link PrettyPrinter} to use when "default pretty-printing"
* 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
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,15 +331,26 @@ protected final JsonNode deserializeAny(JsonParser p, DeserializationContext ctx
protected final JsonNode _fromInt(JsonParser p, DeserializationContext ctxt,
JsonNodeFactory nodeFactory) throws IOException
{
JsonParser.NumberType nt = p.getNumberType();
if (nt == JsonParser.NumberType.BIG_INTEGER
|| ctxt.isEnabled(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS)) {
return nodeFactory.numberNode(p.getBigIntegerValue());
JsonParser.NumberType nt;
int feats = ctxt.getDeserializationFeatures();
if ((feats & F_MASK_INT_COERCIONS) != 0) {
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) {
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -410,21 +410,24 @@ public Double deserializeWithType(JsonParser jp, DeserializationContext ctxt,
@SuppressWarnings("serial")
@JacksonStdImpl
public static class NumberDeserializer
extends StdScalarDeserializer<Number>
extends StdScalarDeserializer<Object>
{
public final static NumberDeserializer instance = new NumberDeserializer();

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

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

case JsonTokenId.ID_NUMBER_FLOAT:
if (ctxt.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)) {
return p.getDecimalValue();
Expand Down Expand Up @@ -462,8 +465,10 @@ public Number deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx
return new BigInteger(text);
}
long value = Long.parseLong(text);
if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {
return Integer.valueOf((int) value);
if (!ctxt.isEnabled(DeserializationFeature.USE_LONG_FOR_INTS)) {
if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {
return Integer.valueOf((int) value);
}
}
return Long.valueOf(value);
} catch (IllegalArgumentException iae) {
Expand All @@ -472,7 +477,7 @@ public Number deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx
case JsonTokenId.ID_START_ARRAY:
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
p.nextToken();
final Number value = deserialize(p, ctxt);
final Object value = deserialize(p, ctxt);
if (p.nextToken() != 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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ public abstract class StdDeserializer<T>
{
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
* 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); }

/*
/****************************************************
/* 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
Original file line number Diff line number Diff line change
Expand Up @@ -240,11 +240,11 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx
if (_numberDeserializer != null) {
return _numberDeserializer.deserialize(p, ctxt);
}
/* [JACKSON-100]: caller may want to get all integral values
* returned as BigInteger, for consistency
/* Caller may want to get all integral values returned as {@link java.math.BigInteger},
* or {@link java.lang.Long} for consistency
*/
if (ctxt.isEnabled(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS)) {
return p.getBigIntegerValue(); // should be optimal, whatever it is
if (ctxt.hasSomeOfFeatures(F_MASK_INT_COERCIONS)) {
return _coerceIntegral(p, ctxt);
}
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) {
return _numberDeserializer.deserialize(p, ctxt);
}
// For [JACKSON-100], see above:
if (ctxt.isEnabled(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS)) {
return p.getBigIntegerValue();
// May need coercion to "bigger" types:
if (ctxt.hasSomeOfFeatures(F_MASK_INT_COERCIONS)) {
return _coerceIntegral(p, ctxt);
}
/* and as per [JACKSON-839], allow "upgrade" to bigger types: out-of-range
* entries can not be produced without type, so this should "just work",
* even if it is bit unclean
*/
return p.getNumberValue();
return p.getNumberValue(); // should be optimal, whatever it is

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

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

@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:
{
JsonToken t = jp.nextToken();
JsonToken t = p.nextToken();
if (t == JsonToken.END_OBJECT) {
return new LinkedHashMap<String,Object>(2);
}
}
case JsonTokenId.ID_FIELD_NAME:
return mapObject(jp, ctxt);
return mapObject(p, ctxt);
case JsonTokenId.ID_START_ARRAY:
{
JsonToken t = jp.nextToken();
JsonToken t = p.nextToken();
if (t == JsonToken.END_ARRAY) { // and empty one too
if (ctxt.isEnabled(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY)) {
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)) {
return mapArrayToArray(jp, ctxt);
return mapArrayToArray(p, ctxt);
}
return mapArray(jp, ctxt);
return mapArray(p, ctxt);
case JsonTokenId.ID_EMBEDDED_OBJECT:
return jp.getEmbeddedObject();
return p.getEmbeddedObject();
case JsonTokenId.ID_STRING:
return jp.getText();
return p.getText();

case JsonTokenId.ID_NUMBER_INT:
if (ctxt.isEnabled(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS)) {
return jp.getBigIntegerValue(); // should be optimal, whatever it is
if (ctxt.hasSomeOfFeatures(F_MASK_INT_COERCIONS)) {
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:
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:
return Boolean.TRUE;
Expand Down
Loading

0 comments on commit 23e52ab

Please sign in to comment.