From ca6c3fc55eb74e21fe90e18da33723fb99b22f21 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sat, 10 Oct 2015 16:41:58 -0700 Subject: [PATCH] Fix #965 --- release-notes/VERSION | 2 + .../jackson/databind/ObjectMapper.java | 6 + .../databind/deser/BeanDeserializer.java | 10 +- .../databind/deser/BeanDeserializerBase.java | 4 +- .../deser/BuilderBasedDeserializer.java | 8 +- .../deser/impl/ExternalTypeHandler.java | 17 ++- .../deser/std/TokenBufferDeserializer.java | 8 +- .../impl/AsArrayTypeDeserializer.java | 24 ++-- .../impl/AsPropertyTypeDeserializer.java | 4 +- .../jackson/databind/util/TokenBuffer.java | 108 ++++++++++++------ .../databind/jsontype/TestExternalId.java | 57 +++++++++ 11 files changed, 172 insertions(+), 76 deletions(-) diff --git a/release-notes/VERSION b/release-notes/VERSION index b4e1eb2a35..9c7ab988f7 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -24,6 +24,8 @@ Project: jackson-databind (contributed by Benson M) #949: Report the offending substring when number parsing fails (contributed by Jesse W) +#965: BigDecimal values via @JsonTypeInfo/@JsonSubTypes get rounded + (reported by gmjabs@github) 2.6.2 (14-Sep-2015) diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java index caa09c304c..2fd10fb56f 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java +++ b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java @@ -2507,6 +2507,9 @@ public T valueToTree(Object fromValue) { if (fromValue == null) return null; TokenBuffer buf = new TokenBuffer(this, false); + if (isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)) { + buf = buf.forceUseOfBigDecimal(true); + } JsonNode result; try { writeValue(buf, fromValue); @@ -3423,6 +3426,9 @@ protected Object _convert(Object fromValue, JavaType toValueType) // Then use TokenBuffer, which is a JsonGenerator: TokenBuffer buf = new TokenBuffer(this, false); + if (isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)) { + buf = buf.forceUseOfBigDecimal(true); + } try { // inlined 'writeValue' with minor changes: // first: disable wrapping when writing diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java index 1886b3dd37..592d07f875 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java @@ -430,7 +430,7 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri } // Ok then, let's collect the whole field; name and value if (unknown == null) { - unknown = new TokenBuffer(p); + unknown = new TokenBuffer(p, ctxt); } unknown.writeFieldName(propName); unknown.copyCurrentStructure(p); @@ -522,7 +522,7 @@ protected Object deserializeWithUnwrapped(JsonParser p, DeserializationContext c if (_propertyBasedCreator != null) { return deserializeUsingPropertyBasedWithUnwrapped(p, ctxt); } - TokenBuffer tokens = new TokenBuffer(p); + TokenBuffer tokens = new TokenBuffer(p, ctxt); tokens.writeStartObject(); final Object bean = _valueInstantiator.createUsingDefault(ctxt); @@ -581,7 +581,7 @@ protected Object deserializeWithUnwrapped(JsonParser p, DeserializationContext c if (t == JsonToken.START_OBJECT) { t = p.nextToken(); } - TokenBuffer tokens = new TokenBuffer(p); + TokenBuffer tokens = new TokenBuffer(p, ctxt); tokens.writeStartObject(); final Class activeView = _needViewProcesing ? ctxt.getActiveView() : null; for (; t == JsonToken.FIELD_NAME; t = p.nextToken()) { @@ -624,7 +624,7 @@ protected Object deserializeUsingPropertyBasedWithUnwrapped(JsonParser p, Deseri final PropertyBasedCreator creator = _propertyBasedCreator; PropertyValueBuffer buffer = creator.startBuilding(p, ctxt, _objectIdReader); - TokenBuffer tokens = new TokenBuffer(p); + TokenBuffer tokens = new TokenBuffer(p, ctxt); tokens.writeStartObject(); JsonToken t = p.getCurrentToken(); @@ -780,7 +780,7 @@ protected Object deserializeUsingPropertyBasedWithExternalTypeId(JsonParser p, D final PropertyBasedCreator creator = _propertyBasedCreator; PropertyValueBuffer buffer = creator.startBuilding(p, ctxt, _objectIdReader); - TokenBuffer tokens = new TokenBuffer(p); + TokenBuffer tokens = new TokenBuffer(p, ctxt); tokens.writeStartObject(); JsonToken t = p.getCurrentToken(); diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java index 03d394dacd..89c60a135f 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java @@ -1037,10 +1037,10 @@ protected Object _handleTypedObjectId(JsonParser jp, DeserializationContext ctxt * @since 2.3 */ @SuppressWarnings("resource") // TokenBuffers don't need close, nor parser thereof - protected Object _convertObjectId(JsonParser jp, DeserializationContext ctxt, + protected Object _convertObjectId(JsonParser p, DeserializationContext ctxt, Object rawId, JsonDeserializer idDeser) throws IOException { - TokenBuffer buf = new TokenBuffer(jp); + TokenBuffer buf = new TokenBuffer(p, ctxt); if (rawId instanceof String) { buf.writeString((String) rawId); } else if (rawId instanceof Long) { diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BuilderBasedDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/BuilderBasedDeserializer.java index b47230c328..8a8ebe7d58 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BuilderBasedDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BuilderBasedDeserializer.java @@ -380,7 +380,7 @@ protected final Object _deserializeUsingPropertyBased(final JsonParser jp, } // Ok then, let's collect the whole field; name and value if (unknown == null) { - unknown = new TokenBuffer(jp); + unknown = new TokenBuffer(jp, ctxt); } unknown.writeFieldName(propName); unknown.copyCurrentStructure(jp); @@ -458,7 +458,7 @@ protected Object deserializeWithUnwrapped(JsonParser jp, DeserializationContext if (_propertyBasedCreator != null) { return deserializeUsingPropertyBasedWithUnwrapped(jp, ctxt); } - TokenBuffer tokens = new TokenBuffer(jp); + TokenBuffer tokens = new TokenBuffer(jp, ctxt); tokens.writeStartObject(); Object bean = _valueInstantiator.createUsingDefault(ctxt); @@ -516,7 +516,7 @@ protected Object deserializeWithUnwrapped(JsonParser jp, if (t == JsonToken.START_OBJECT) { t = jp.nextToken(); } - TokenBuffer tokens = new TokenBuffer(jp); + TokenBuffer tokens = new TokenBuffer(jp, ctxt); tokens.writeStartObject(); final Class activeView = _needViewProcesing ? ctxt.getActiveView() : null; for (; t == JsonToken.FIELD_NAME; t = jp.nextToken()) { @@ -560,7 +560,7 @@ protected Object deserializeUsingPropertyBasedWithUnwrapped(JsonParser p, final PropertyBasedCreator creator = _propertyBasedCreator; PropertyValueBuffer buffer = creator.startBuilding(p, ctxt, _objectIdReader); - TokenBuffer tokens = new TokenBuffer(p); + TokenBuffer tokens = new TokenBuffer(p, ctxt); tokens.writeStartObject(); JsonToken t = p.getCurrentToken(); diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/ExternalTypeHandler.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/ExternalTypeHandler.java index b4e1a266d6..50d0548687 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/ExternalTypeHandler.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/ExternalTypeHandler.java @@ -88,7 +88,7 @@ public boolean handleTypePropertyValue(JsonParser jp, DeserializationContext ctx * * @return True, if the given property was properly handled */ - public boolean handlePropertyValue(JsonParser jp, DeserializationContext ctxt, + public boolean handlePropertyValue(JsonParser p, DeserializationContext ctxt, String propName, Object bean) throws IOException { Integer I = _nameToPropertyIndex.get(propName); @@ -99,13 +99,13 @@ public boolean handlePropertyValue(JsonParser jp, DeserializationContext ctxt, ExtTypedProperty prop = _properties[index]; boolean canDeserialize; if (prop.hasTypePropertyName(propName)) { - _typeIds[index] = jp.getText(); - jp.skipChildren(); + _typeIds[index] = p.getText(); + p.skipChildren(); canDeserialize = (bean != null) && (_tokens[index] != null); } else { @SuppressWarnings("resource") - TokenBuffer tokens = new TokenBuffer(jp); - tokens.copyCurrentStructure(jp); + TokenBuffer tokens = new TokenBuffer(p, ctxt); + tokens.copyCurrentStructure(p); _tokens[index] = tokens; canDeserialize = (bean != null) && (_typeIds[index] != null); } @@ -116,7 +116,7 @@ public boolean handlePropertyValue(JsonParser jp, DeserializationContext ctxt, String typeId = _typeIds[index]; // clear stored data, to avoid deserializing+setting twice: _typeIds[index] = null; - _deserializeAndSet(jp, ctxt, bean, index, typeId); + _deserializeAndSet(p, ctxt, bean, index, typeId); _tokens[index] = null; } return true; @@ -228,8 +228,7 @@ protected final Object _deserialize(JsonParser p, DeserializationContext ctxt, if (t == JsonToken.VALUE_NULL) { return null; } - - TokenBuffer merged = new TokenBuffer(p); + TokenBuffer merged = new TokenBuffer(p, ctxt); merged.writeStartArray(); merged.writeString(typeId); merged.copyCurrentStructure(p2); @@ -255,7 +254,7 @@ protected final void _deserializeAndSet(JsonParser p, DeserializationContext ctx _properties[index].getProperty().set(bean, null); return; } - TokenBuffer merged = new TokenBuffer(p); + TokenBuffer merged = new TokenBuffer(p, ctxt); merged.writeStartArray(); merged.writeString(typeId); 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 index 0d5b755d81..5435e97f48 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/TokenBufferDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/TokenBufferDeserializer.java @@ -27,11 +27,11 @@ public class TokenBufferDeserializer extends StdScalarDeserializer public TokenBufferDeserializer() { super(TokenBuffer.class); } @Override - public TokenBuffer deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { - return createBufferInstance(jp).deserialize(jp, ctxt); + public TokenBuffer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + return createBufferInstance(p).deserialize(p, ctxt); } - protected TokenBuffer createBufferInstance(JsonParser jp) { - return new TokenBuffer(jp); + protected TokenBuffer createBufferInstance(JsonParser p) { + return new TokenBuffer(p); } } \ No newline at end of file diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsArrayTypeDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsArrayTypeDeserializer.java index 249adec892..f5fdb2be0e 100644 --- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsArrayTypeDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsArrayTypeDeserializer.java @@ -80,17 +80,17 @@ public Object deserializeTypedFromAny(JsonParser jp, DeserializationContext ctxt * deserialization. */ @SuppressWarnings("resource") - protected Object _deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException + protected Object _deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { // 02-Aug-2013, tatu: May need to use native type ids - if (jp.canReadTypeId()) { - Object typeId = jp.getTypeId(); + if (p.canReadTypeId()) { + Object typeId = p.getTypeId(); if (typeId != null) { - return _deserializeWithNativeTypeId(jp, ctxt, typeId); + return _deserializeWithNativeTypeId(p, ctxt, typeId); } } - boolean hadStartArray = jp.isExpectedStartArrayToken(); - String typeId = _locateTypeId(jp, ctxt); + boolean hadStartArray = p.isExpectedStartArrayToken(); + String typeId = _locateTypeId(p, ctxt); JsonDeserializer deser = _findDeserializer(ctxt, typeId); // Minor complication: we may need to merge type id in? if (_typeIdVisible @@ -98,19 +98,19 @@ protected Object _deserialize(JsonParser jp, DeserializationContext ctxt) throws // internal and external properties // TODO: but does it need to be injected in external case? Why not? && !_usesExternalId() - && jp.getCurrentToken() == JsonToken.START_OBJECT) { + && p.getCurrentToken() == JsonToken.START_OBJECT) { // but what if there's nowhere to add it in? Error? Or skip? For now, skip. TokenBuffer tb = new TokenBuffer(null, false); tb.writeStartObject(); // recreate START_OBJECT tb.writeFieldName(_typePropertyName); tb.writeString(typeId); - jp = JsonParserSequence.createFlattened(tb.asParser(jp), jp); - jp.nextToken(); + p = JsonParserSequence.createFlattened(tb.asParser(p), p); + p.nextToken(); } - Object value = deser.deserialize(jp, ctxt); + Object value = deser.deserialize(p, ctxt); // And then need the closing END_ARRAY - if (hadStartArray && jp.nextToken() != JsonToken.END_ARRAY) { - throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY, + if (hadStartArray && p.nextToken() != JsonToken.END_ARRAY) { + throw ctxt.wrongTokenException(p, JsonToken.END_ARRAY, "expected closing END_ARRAY after type information and deserialized value"); } return value; diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsPropertyTypeDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsPropertyTypeDeserializer.java index ae8b5397f8..cd13eb69c5 100644 --- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsPropertyTypeDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsPropertyTypeDeserializer.java @@ -93,7 +93,7 @@ public Object deserializeTypedFromObject(JsonParser jp, DeserializationContext c return _deserializeTypedForId(jp, ctxt, tb); } if (tb == null) { - tb = new TokenBuffer(null, false); + tb = new TokenBuffer(jp, ctxt); } tb.writeFieldName(name); tb.copyCurrentStructure(jp); @@ -108,7 +108,7 @@ protected Object _deserializeTypedForId(JsonParser jp, DeserializationContext ct JsonDeserializer deser = _findDeserializer(ctxt, typeId); if (_typeIdVisible) { // need to merge id back in JSON input? if (tb == null) { - tb = new TokenBuffer(null, false); + tb = new TokenBuffer(jp, ctxt); } tb.writeFieldName(jp.getCurrentName()); tb.writeString(typeId); diff --git a/src/main/java/com/fasterxml/jackson/databind/util/TokenBuffer.java b/src/main/java/com/fasterxml/jackson/databind/util/TokenBuffer.java index 3ade2b4e29..fc3600ad96 100644 --- a/src/main/java/com/fasterxml/jackson/databind/util/TokenBuffer.java +++ b/src/main/java/com/fasterxml/jackson/databind/util/TokenBuffer.java @@ -70,6 +70,14 @@ public class TokenBuffer * @since 2.3 */ protected boolean _mayHaveNativeIds; + + /** + * Flag set during construction, if use of {@link BigDecimal} is to be forced + * on all floating-point values. + * + * @since 2.7 + */ + protected boolean _forceBigDecimal; /* /********************************************************** @@ -160,19 +168,36 @@ public TokenBuffer(ObjectCodec codec, boolean hasNativeIds) /** * @since 2.3 */ - public TokenBuffer(JsonParser jp) + public TokenBuffer(JsonParser p) { + this(p, null); + } + + /** + * @since 2.7 + */ + public TokenBuffer(JsonParser p, DeserializationContext ctxt) { - _objectCodec = jp.getCodec(); + _objectCodec = p.getCodec(); _generatorFeatures = DEFAULT_GENERATOR_FEATURES; _writeContext = JsonWriteContext.createRootContext(null); // at first we have just one segment _first = _last = new Segment(); _appendAt = 0; - _hasNativeTypeIds = jp.canReadTypeId(); - _hasNativeObjectIds = jp.canReadObjectId(); + _hasNativeTypeIds = p.canReadTypeId(); + _hasNativeObjectIds = p.canReadObjectId(); _mayHaveNativeIds = _hasNativeTypeIds | _hasNativeObjectIds; + _forceBigDecimal = (ctxt == null) ? false + : ctxt.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS); } - + + /** + * @since 2.7 + */ + public TokenBuffer forceUseOfBigDecimal(boolean b) { + _forceBigDecimal = b; + return this; + } + @Override public Version version() { return com.fasterxml.jackson.databind.cfg.PackageVersion.VERSION; @@ -897,12 +922,12 @@ public void writeObjectId(Object id) { */ @Override - public void copyCurrentEvent(JsonParser jp) throws IOException + public void copyCurrentEvent(JsonParser p) throws IOException { if (_mayHaveNativeIds) { - _checkNativeIds(jp); + _checkNativeIds(p); } - switch (jp.getCurrentToken()) { + switch (p.getCurrentToken()) { case START_OBJECT: writeStartObject(); break; @@ -916,37 +941,46 @@ public void copyCurrentEvent(JsonParser jp) throws IOException writeEndArray(); break; case FIELD_NAME: - writeFieldName(jp.getCurrentName()); + writeFieldName(p.getCurrentName()); break; case VALUE_STRING: - if (jp.hasTextCharacters()) { - writeString(jp.getTextCharacters(), jp.getTextOffset(), jp.getTextLength()); + if (p.hasTextCharacters()) { + writeString(p.getTextCharacters(), p.getTextOffset(), p.getTextLength()); } else { - writeString(jp.getText()); + writeString(p.getText()); } break; case VALUE_NUMBER_INT: - switch (jp.getNumberType()) { + switch (p.getNumberType()) { case INT: - writeNumber(jp.getIntValue()); + writeNumber(p.getIntValue()); break; case BIG_INTEGER: - writeNumber(jp.getBigIntegerValue()); + writeNumber(p.getBigIntegerValue()); break; default: - writeNumber(jp.getLongValue()); + writeNumber(p.getLongValue()); } break; case VALUE_NUMBER_FLOAT: - switch (jp.getNumberType()) { - case BIG_DECIMAL: - writeNumber(jp.getDecimalValue()); - break; - case FLOAT: - writeNumber(jp.getFloatValue()); - break; - default: - writeNumber(jp.getDoubleValue()); + if (_forceBigDecimal) { + /* 10-Oct-2015, tatu: Ideally we would first determine whether underlying + * number is already decoded into a number (in which case might as well + * access as number); or is still retained as text (in which case we + * should further defer decoding that may not need BigDecimal): + */ + writeNumber(p.getDecimalValue()); + } else { + switch (p.getNumberType()) { + case BIG_DECIMAL: + writeNumber(p.getDecimalValue()); + break; + case FLOAT: + writeNumber(p.getFloatValue()); + break; + default: + writeNumber(p.getDoubleValue()); + } } break; case VALUE_TRUE: @@ -959,7 +993,7 @@ public void copyCurrentEvent(JsonParser jp) throws IOException writeNull(); break; case VALUE_EMBEDDED_OBJECT: - writeObject(jp.getEmbeddedObject()); + writeObject(p.getEmbeddedObject()); break; default: throw new RuntimeException("Internal error: should never end up through this code path"); @@ -1164,8 +1198,7 @@ public Version version() { /********************************************************** */ - public JsonToken peekNextToken() - throws IOException, JsonParseException + public JsonToken peekNextToken() throws IOException { // closed? nothing more to peek, either if (_closed) return null; @@ -1354,7 +1387,7 @@ public boolean hasTextCharacters() { */ @Override - public BigInteger getBigIntegerValue() throws IOException, JsonParseException + public BigInteger getBigIntegerValue() throws IOException { Number n = getNumberValue(); if (n instanceof BigInteger) { @@ -1368,7 +1401,7 @@ public BigInteger getBigIntegerValue() throws IOException, JsonParseException } @Override - public BigDecimal getDecimalValue() throws IOException, JsonParseException + public BigDecimal getDecimalValue() throws IOException { Number n = getNumberValue(); if (n instanceof BigDecimal) { @@ -1387,17 +1420,17 @@ public BigDecimal getDecimalValue() throws IOException, JsonParseException } @Override - public double getDoubleValue() throws IOException, JsonParseException { + public double getDoubleValue() throws IOException { return getNumberValue().doubleValue(); } @Override - public float getFloatValue() throws IOException, JsonParseException { + public float getFloatValue() throws IOException { return getNumberValue().floatValue(); } @Override - public int getIntValue() throws IOException, JsonParseException + public int getIntValue() throws IOException { // optimize common case: if (_currToken == JsonToken.VALUE_NUMBER_INT) { @@ -1407,12 +1440,12 @@ public int getIntValue() throws IOException, JsonParseException } @Override - public long getLongValue() throws IOException, JsonParseException { + public long getLongValue() throws IOException { return getNumberValue().longValue(); } @Override - public NumberType getNumberType() throws IOException, JsonParseException + public NumberType getNumberType() throws IOException { Number n = getNumberValue(); if (n instanceof Integer) return NumberType.INT; @@ -1426,7 +1459,7 @@ public NumberType getNumberType() throws IOException, JsonParseException } @Override - public final Number getNumberValue() throws IOException, JsonParseException { + public final Number getNumberValue() throws IOException { _checkIsNumber(); Object value = _currentObject(); if (value instanceof Number) { @@ -1495,8 +1528,7 @@ public byte[] getBinaryValue(Base64Variant b64variant) throws IOException, JsonP } @Override - public int readBinaryValue(Base64Variant b64variant, OutputStream out) - throws IOException, JsonParseException + public int readBinaryValue(Base64Variant b64variant, OutputStream out) throws IOException { byte[] data = getBinaryValue(b64variant); if (data != null) { diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestExternalId.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestExternalId.java index 0e5707f67e..3186c520f0 100644 --- a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestExternalId.java +++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestExternalId.java @@ -1,11 +1,13 @@ package com.fasterxml.jackson.databind.jsontype; +import java.math.BigDecimal; import java.util.*; import com.fasterxml.jackson.annotation.*; import com.fasterxml.jackson.annotation.JsonTypeInfo.As; import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; import com.fasterxml.jackson.databind.BaseMapTest; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; // Tests for External type id, one that exists at same level as typed Object, @@ -508,4 +510,59 @@ public void testInverseExternalId928() throws Exception assertNotNull(envelope2); assertEquals(Payload928.class, envelope2._payload.getClass()); } + + enum Type965 { BIG_DECIMAL } + + static class Wrapper965 { + protected Type965 typeEnum; + + protected Object value; + + @JsonGetter("type") + String getTypeString() { + return typeEnum.name(); + } + + @JsonSetter("type") + void setTypeString(String type) { + this.typeEnum = Type965.valueOf(type); + } + + @JsonGetter(value = "objectValue") + Object getValue() { + return value; + } + + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type") + @JsonSubTypes({ @JsonSubTypes.Type(name = "BIG_DECIMAL", value = BigDecimal.class) }) + @JsonSetter(value = "objectValue") + private void setValue(Object value) { + this.value = value; + } + } + // for [databind#965] + public void testBigDecimal965() throws Exception + { + + Wrapper965 w = new Wrapper965(); + w.typeEnum = Type965.BIG_DECIMAL; + final String NUM_STR = "-10000000000.0000000001"; + w.value = new BigDecimal(NUM_STR); + + String json = MAPPER.writeValueAsString(w); + + // simple sanity check so serialization is faithful + if (!json.contains(NUM_STR)) { + fail("JSON content should contain value '"+NUM_STR+"', does not appear to: "+json); + } + + Wrapper965 w2 = MAPPER.readerFor(Wrapper965.class) + .with(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS) + .readValue(json); + + assertEquals(w.typeEnum, w2.typeEnum); + assertTrue(String.format("Expected %s = %s; got back %s = %s", + w.value.getClass().getSimpleName(), w.value.toString(), w2.value.getClass().getSimpleName(), w2.value.toString()), + w.value.equals(w2.value)); + } }