Skip to content

Commit 9a7f9ab

Browse files
swallezgithub-actions[bot]
authored andcommitted
Ensure double values stay double even if they fit in an integer (#289)
1 parent efe20a6 commit 9a7f9ab

File tree

2 files changed

+175
-1
lines changed

2 files changed

+175
-1
lines changed

java-client/src/main/java/co/elastic/clients/json/jackson/JsonValueParser.java

+97-1
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,16 @@
2424
import com.fasterxml.jackson.core.JsonToken;
2525
import jakarta.json.JsonArray;
2626
import jakarta.json.JsonArrayBuilder;
27+
import jakarta.json.JsonNumber;
2728
import jakarta.json.JsonObject;
2829
import jakarta.json.JsonObjectBuilder;
2930
import jakarta.json.JsonValue;
3031
import jakarta.json.spi.JsonProvider;
3132
import jakarta.json.stream.JsonParsingException;
3233

3334
import java.io.IOException;
35+
import java.math.BigDecimal;
36+
import java.math.BigInteger;
3437

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

6770
public JsonValue parseValue(JsonParser parser) throws IOException {
71+
JsonToken jsonToken = parser.currentToken();
6872
switch (parser.currentToken()) {
6973
case START_OBJECT:
7074
return parseObject(parser);
@@ -93,7 +97,8 @@ public JsonValue parseValue(JsonParser parser) throws IOException {
9397
return provider.createValue(parser.getLongValue());
9498
case FLOAT:
9599
case DOUBLE:
96-
return provider.createValue(parser.getDoubleValue());
100+
// Use double also for floats, as JSON-P has no support for float
101+
return new DoubleNumber(parser.getDoubleValue());
97102
case BIG_DECIMAL:
98103
return provider.createValue(parser.getDecimalValue());
99104
case BIG_INTEGER:
@@ -105,4 +110,95 @@ public JsonValue parseValue(JsonParser parser) throws IOException {
105110

106111
}
107112
}
113+
114+
private static class DoubleNumber implements JsonNumber {
115+
116+
private final double value;
117+
118+
DoubleNumber(double value) {
119+
this.value = value;
120+
}
121+
122+
@Override
123+
public boolean isIntegral() {
124+
return false;
125+
}
126+
127+
@Override
128+
public int intValue() {
129+
return (int) value;
130+
}
131+
132+
@Override
133+
public int intValueExact() {
134+
int result = (int) value;
135+
136+
if ((double)result == value) {
137+
return result;
138+
} else {
139+
throw new ArithmeticException();
140+
}
141+
}
142+
143+
@Override
144+
public long longValue() {
145+
return (long) value;
146+
}
147+
148+
@Override
149+
public long longValueExact() {
150+
long result = (long) value;
151+
152+
if ((double)result == value) {
153+
return result;
154+
} else {
155+
throw new ArithmeticException();
156+
}
157+
}
158+
159+
@Override
160+
public BigInteger bigIntegerValue() {
161+
return bigDecimalValue().toBigInteger();
162+
}
163+
164+
@Override
165+
public BigInteger bigIntegerValueExact() {
166+
return bigDecimalValue().toBigIntegerExact();
167+
}
168+
169+
@Override
170+
public double doubleValue() {
171+
return value;
172+
}
173+
174+
@Override
175+
public BigDecimal bigDecimalValue() {
176+
return new BigDecimal(value);
177+
}
178+
179+
@Override
180+
public ValueType getValueType() {
181+
return ValueType.NUMBER;
182+
}
183+
184+
@Override
185+
public Number numberValue() {
186+
return value;
187+
}
188+
189+
@Override
190+
public String toString() {
191+
return String.valueOf(value);
192+
}
193+
194+
@Override
195+
public int hashCode() {
196+
return Double.hashCode(value);
197+
}
198+
199+
@Override
200+
public boolean equals(Object obj) {
201+
return obj instanceof DoubleNumber && ((DoubleNumber)obj).value == value;
202+
}
203+
}
108204
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package co.elastic.clients.elasticsearch.json.jackson;
21+
22+
import co.elastic.clients.json.JsonpMapper;
23+
import co.elastic.clients.json.JsonpUtils;
24+
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
25+
import jakarta.json.JsonObject;
26+
import jakarta.json.JsonValue;
27+
import jakarta.json.stream.JsonParser;
28+
import org.junit.Assert;
29+
import org.junit.Test;
30+
31+
import java.io.StringReader;
32+
import java.util.Map;
33+
34+
public class JsonValueParserTest extends Assert {
35+
36+
public static class Data {
37+
public Map<String, Object> data;
38+
}
39+
40+
@Test
41+
public void testFloatsShouldDeserializeAsFloats() throws Exception {
42+
// When using Jackson to target a map of objects, values with a decimal separator
43+
// should deserialize as a double even if they fit in an int or long.
44+
// See https://github.com/elastic/elasticsearch-java/issues/156
45+
46+
String json = "{\"data\": {\"value\": 1.4778125E7, \"value2\": 1.4778125E7 }}";
47+
JsonpMapper mapper = new JacksonJsonpMapper();
48+
49+
{
50+
JsonParser parser = mapper.jsonProvider().createParser(new StringReader(json));
51+
Data data = mapper.deserialize(parser, Data.class);
52+
53+
Double d = (Double)data.data.get("value");
54+
assertEquals(1.4778125E7, d, 0.001);
55+
}
56+
57+
{
58+
// Test with buffering used in union resolution
59+
JsonParser parser = mapper.jsonProvider().createParser(new StringReader(json));
60+
parser.next();
61+
JsonObject object = parser.getObject();
62+
63+
// Test equals/hashcode
64+
JsonValue v = object.getJsonObject("data").get("value");
65+
JsonValue v2 = object.getJsonObject("data").get("value2");
66+
67+
assertEquals(v.hashCode(), v2.hashCode());
68+
assertEquals(v, v2);
69+
70+
parser = JsonpUtils.objectParser(object, mapper);
71+
Data data = mapper.deserialize(parser, Data.class);
72+
73+
Double d = (Double)data.data.get("value");
74+
assertEquals(1.4778125E7, d, 0.001);
75+
}
76+
77+
}
78+
}

0 commit comments

Comments
 (0)