Skip to content

Fix TokenBuffer._copyBufferValue floating point values #2982

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions src/main/java/com/fasterxml/jackson/databind/util/TokenBuffer.java
Original file line number Diff line number Diff line change
Expand Up @@ -1220,18 +1220,32 @@ private void _copyBufferValue(JsonParser p, JsonToken t) throws IOException
case BIG_INTEGER:
writeNumber(p.getBigIntegerValue());
break;
default:
case LONG:
writeNumber(p.getLongValue());
break;
default:
throw new RuntimeException(
"Internal error: unexpected VALUE_NUMBER_INT type: " + p.getNumberType());
}
break;
case VALUE_NUMBER_FLOAT:
if (_forceBigDecimal) {
writeNumber(p.getDecimalValue());
} else {
// 09-Jul-2020, tatu: Used to just copy using most optimal method, but
// issues like [databind#2644] force to use exact, not optimal type
final Number n = p.getNumberValueExact();
_appendValue(JsonToken.VALUE_NUMBER_FLOAT, n);
switch (p.getNumberType()) {
case FLOAT:
writeNumber(p.getFloatValue());
break;
case DOUBLE:
writeNumber(p.getDoubleValue());
break;
case BIG_DECIMAL:
writeNumber(p.getDecimalValue());
break;
default:
throw new RuntimeException(
"Internal error: unexpected VALUE_NUMBER_FLOAT type: " + p.getNumberType());
}
}
break;
case VALUE_TRUE:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ public void testForceIntsToLongs() throws Exception
public void testBigDecimalSubtypes() throws Exception
{
ObjectMapper mapper = jsonMapperBuilder()
.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
.registerSubtypes(NodeParent2644.class)
.build();
NodeRoot2644 root = mapper.readValue(
Expand All @@ -356,8 +357,8 @@ public void testBigDecimalSubtypes() throws Exception
// [databind#2784]
public void testBigDecimalUnwrapped() throws Exception
{
final ObjectMapper mapper = newJsonMapper();
// mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
final ObjectMapper mapper = newJsonMapper()
.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
final String JSON = "{\"value\": 5.00}";
NestedBigDecimalHolder2784 result = mapper.readValue(JSON, NestedBigDecimalHolder2784.class);
assertEquals(new BigDecimal("5.00"), result.holder.value);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
package com.fasterxml.jackson.databind.jsontype;

import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.BaseMapTest;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class TestDoubleJsonCreator extends BaseMapTest {

public static final class UnionExample {
private final Base value;

@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
private UnionExample(Base value) {
this.value = value;
}

@JsonValue
private Base getValue() {
return value;
}

public static UnionExample double_(AliasDouble value) {
return new UnionExample(new DoubleWrapper(value));
}

public <T> T accept(Visitor<T> visitor) {
return value.accept(visitor);
}

@Override
public boolean equals(Object other) {
return this == other || (other instanceof UnionExample && equalTo((UnionExample) other));
}

private boolean equalTo(UnionExample other) {
return this.value.equals(other.value);
}

@Override
public int hashCode() {
return Objects.hashCode(this.value);
}

@Override
public String toString() {
return "UnionExample{value: " + value + '}';
}

public interface Visitor<T> {
T visitDouble(AliasDouble value);

T visitUnknown(String unknownType);
}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", visible = true, defaultImpl = UnknownWrapper.class)
@JsonSubTypes(@JsonSubTypes.Type(UnionExample.DoubleWrapper.class))
@JsonIgnoreProperties(ignoreUnknown = true)
private interface Base {
<T> T accept(Visitor<T> visitor);
}

@JsonTypeName("double")
private static final class DoubleWrapper implements Base {
private final AliasDouble value;

@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
private DoubleWrapper(@JsonSetter("double") AliasDouble value) {
Objects.requireNonNull(value, "double cannot be null");
this.value = value;
}

@JsonProperty("double")
private AliasDouble getValue() {
return value;
}

@Override
public <T> T accept(Visitor<T> visitor) {
return visitor.visitDouble(value);
}

@Override
public boolean equals(Object other) {
return this == other || (other instanceof DoubleWrapper && equalTo((DoubleWrapper) other));
}

private boolean equalTo(DoubleWrapper other) {
return this.value.equals(other.value);
}

@Override
public int hashCode() {
return Objects.hashCode(this.value);
}

@Override
public String toString() {
return "DoubleWrapper{value: " + value + '}';
}
}

@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "type",
visible = true)
private static final class UnknownWrapper implements Base {
private final String type;

private final Map<String, Object> value;

@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
private UnknownWrapper(@JsonProperty("type") String type) {
this(type, new HashMap<String, Object>());
}

private UnknownWrapper(String type, Map<String, Object> value) {
Objects.requireNonNull(type, "type cannot be null");
Objects.requireNonNull(value, "value cannot be null");
this.type = type;
this.value = value;
}

@JsonProperty
private String getType() {
return type;
}

@JsonAnyGetter
private Map<String, Object> getValue() {
return value;
}

@JsonAnySetter
private void put(String key, Object val) {
value.put(key, val);
}

@Override
public <T> T accept(Visitor<T> visitor) {
return visitor.visitUnknown(type);
}

@Override
public boolean equals(Object other) {
return this == other || (other instanceof UnknownWrapper && equalTo((UnknownWrapper) other));
}

private boolean equalTo(UnknownWrapper other) {
return this.type.equals(other.type) && this.value.equals(other.value);
}

@Override
public int hashCode() {
return Objects.hash(this.type, this.value);
}

@Override
public String toString() {
return "UnknownWrapper{type: " + type + ", value: " + value + '}';
}
}
}

public static final class AliasDouble {
private final double value;

private AliasDouble(double value) {
this.value = value;
}

@JsonValue
public double get() {
return value;
}

@Override
public String toString() {
return String.valueOf(value);
}

@Override
public boolean equals(Object other) {
return this == other
|| (other instanceof AliasDouble
&& Double.doubleToLongBits(this.value) == Double.doubleToLongBits(((AliasDouble) other).value));
}

@Override
public int hashCode() {
return Objects.hashCode(value);
}

@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static AliasDouble of(double value) {
return new AliasDouble(value);
}
}

public void testDeserializationTypeFieldLast() throws IOException {
UnionExample expected = UnionExample.double_(AliasDouble.of(2.0D));
UnionExample actual = objectMapper().readValue(
a2q("{'double': 2.0,'type':'double'}"),
new TypeReference<UnionExample>() {});
assertEquals(expected, actual);
}

public void testDeserializationTypeFieldFirst() throws IOException {
UnionExample expected = UnionExample.double_(AliasDouble.of(2.0D));
UnionExample actual = objectMapper().readValue(
a2q("{'type':'double','double': 2.0}"),
new TypeReference<UnionExample>() {});
assertEquals(expected, actual);
}
}