Skip to content

Commit

Permalink
Ensure double values stay double even if they fit in an integer (#289)
Browse files Browse the repository at this point in the history
  • Loading branch information
swallez authored and github-actions[bot] committed May 23, 2022
1 parent efe20a6 commit 9a7f9ab
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@
import com.fasterxml.jackson.core.JsonToken;
import jakarta.json.JsonArray;
import jakarta.json.JsonArrayBuilder;
import jakarta.json.JsonNumber;
import jakarta.json.JsonObject;
import jakarta.json.JsonObjectBuilder;
import jakarta.json.JsonValue;
import jakarta.json.spi.JsonProvider;
import jakarta.json.stream.JsonParsingException;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;

/**
* Reads a Jsonp value/object/array from a Jackson parser. The parser's current token should be the start of the
Expand Down Expand Up @@ -65,6 +68,7 @@ public JsonArray parseArray(JsonParser parser) throws IOException {
}

public JsonValue parseValue(JsonParser parser) throws IOException {
JsonToken jsonToken = parser.currentToken();
switch (parser.currentToken()) {
case START_OBJECT:
return parseObject(parser);
Expand Down Expand Up @@ -93,7 +97,8 @@ public JsonValue parseValue(JsonParser parser) throws IOException {
return provider.createValue(parser.getLongValue());
case FLOAT:
case DOUBLE:
return provider.createValue(parser.getDoubleValue());
// Use double also for floats, as JSON-P has no support for float
return new DoubleNumber(parser.getDoubleValue());
case BIG_DECIMAL:
return provider.createValue(parser.getDecimalValue());
case BIG_INTEGER:
Expand All @@ -105,4 +110,95 @@ public JsonValue parseValue(JsonParser parser) throws IOException {

}
}

private static class DoubleNumber implements JsonNumber {

private final double value;

DoubleNumber(double value) {
this.value = value;
}

@Override
public boolean isIntegral() {
return false;
}

@Override
public int intValue() {
return (int) value;
}

@Override
public int intValueExact() {
int result = (int) value;

if ((double)result == value) {
return result;
} else {
throw new ArithmeticException();
}
}

@Override
public long longValue() {
return (long) value;
}

@Override
public long longValueExact() {
long result = (long) value;

if ((double)result == value) {
return result;
} else {
throw new ArithmeticException();
}
}

@Override
public BigInteger bigIntegerValue() {
return bigDecimalValue().toBigInteger();
}

@Override
public BigInteger bigIntegerValueExact() {
return bigDecimalValue().toBigIntegerExact();
}

@Override
public double doubleValue() {
return value;
}

@Override
public BigDecimal bigDecimalValue() {
return new BigDecimal(value);
}

@Override
public ValueType getValueType() {
return ValueType.NUMBER;
}

@Override
public Number numberValue() {
return value;
}

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

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

@Override
public boolean equals(Object obj) {
return obj instanceof DoubleNumber && ((DoubleNumber)obj).value == value;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package co.elastic.clients.elasticsearch.json.jackson;

import co.elastic.clients.json.JsonpMapper;
import co.elastic.clients.json.JsonpUtils;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import jakarta.json.JsonObject;
import jakarta.json.JsonValue;
import jakarta.json.stream.JsonParser;
import org.junit.Assert;
import org.junit.Test;

import java.io.StringReader;
import java.util.Map;

public class JsonValueParserTest extends Assert {

public static class Data {
public Map<String, Object> data;
}

@Test
public void testFloatsShouldDeserializeAsFloats() throws Exception {
// When using Jackson to target a map of objects, values with a decimal separator
// should deserialize as a double even if they fit in an int or long.
// See https://github.com/elastic/elasticsearch-java/issues/156

String json = "{\"data\": {\"value\": 1.4778125E7, \"value2\": 1.4778125E7 }}";
JsonpMapper mapper = new JacksonJsonpMapper();

{
JsonParser parser = mapper.jsonProvider().createParser(new StringReader(json));
Data data = mapper.deserialize(parser, Data.class);

Double d = (Double)data.data.get("value");
assertEquals(1.4778125E7, d, 0.001);
}

{
// Test with buffering used in union resolution
JsonParser parser = mapper.jsonProvider().createParser(new StringReader(json));
parser.next();
JsonObject object = parser.getObject();

// Test equals/hashcode
JsonValue v = object.getJsonObject("data").get("value");
JsonValue v2 = object.getJsonObject("data").get("value2");

assertEquals(v.hashCode(), v2.hashCode());
assertEquals(v, v2);

parser = JsonpUtils.objectParser(object, mapper);
Data data = mapper.deserialize(parser, Data.class);

Double d = (Double)data.data.get("value");
assertEquals(1.4778125E7, d, 0.001);
}

}
}

0 comments on commit 9a7f9ab

Please sign in to comment.