Skip to content

Commit beefb28

Browse files
authored
Merge pull request #68 from epics-base/Issue_66
Issue_66 Support NaN, Inf and -Inf in JSON serialization
2 parents fd140f3 + 5ad2246 commit beefb28

File tree

6 files changed

+138
-12
lines changed

6 files changed

+138
-12
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,7 @@ nbactions.xml
1515

1616
# For all you Mac OS users out there
1717
**/.DS_Store
18+
19+
# For all you IntelliJ users out there
20+
**/*.iml
21+
**/.idea

epics-vtype/vtype-json/src/main/java/org/epics/vtype/json/JsonArrays.java

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,7 @@ public static boolean isStringArray(JsonArray array) {
8282
public static ListDouble toListDouble(JsonArray array) {
8383
double[] values = new double[array.size()];
8484
for (int i = 0; i < values.length; i++) {
85-
if (array.isNull(i)) {
86-
values[i] = Double.NaN;
87-
} else {
88-
values[i] = array.getJsonNumber(i).doubleValue();
89-
}
85+
values[i] = VTypeToJsonV1.getDoubleFromJsonString(array.get(i).toString());
9086
}
9187
return ArrayDouble.of(values);
9288
}
@@ -313,11 +309,17 @@ public static JsonArrayBuilder fromListNumber(ListNumber list) {
313309
} else {
314310
for (int i = 0; i < list.size(); i++) {
315311
double value = list.getDouble(i);
316-
if (Double.isNaN(value) || Double.isInfinite(value)) {
317-
b.addNull();
318-
} else {
312+
if(Double.isFinite(value)){
319313
b.add(value);
320314
}
315+
else if (Double.isNaN(value)) {
316+
b.add(VTypeJsonMapper.NAN);
317+
} else if(Double.valueOf(value).equals(Double.POSITIVE_INFINITY)){
318+
b.add(VTypeJsonMapper.POS_INF);
319+
}
320+
else if(Double.valueOf(value).equals(Double.NEGATIVE_INFINITY)){
321+
b.add(VTypeJsonMapper.NEG_INF);
322+
}
321323
}
322324
}
323325
return b;

epics-vtype/vtype-json/src/main/java/org/epics/vtype/json/JsonVTypeBuilder.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,17 @@ public JsonVTypeBuilder add(String string, long l) {
7272

7373
@Override
7474
public JsonVTypeBuilder add(String string, double d) {
75-
if (Double.isNaN(d) || Double.isInfinite(d)) {
76-
builder.addNull(string);
77-
} else {
75+
if(Double.isFinite(d)){
7876
builder.add(string, d);
7977
}
78+
else if (Double.isNaN(d)) {
79+
builder.add(string, VTypeJsonMapper.NAN);
80+
} else if(Double.valueOf(d).equals(Double.POSITIVE_INFINITY)){
81+
builder.add(string, VTypeJsonMapper.POS_INF);
82+
}
83+
else if(Double.valueOf(d).equals(Double.NEGATIVE_INFINITY)){
84+
builder.add(string, VTypeJsonMapper.NEG_INF);
85+
}
8086
return this;
8187
}
8288

epics-vtype/vtype-json/src/main/java/org/epics/vtype/json/VTypeJsonMapper.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,25 @@ class VTypeJsonMapper implements JsonObject {
4444

4545
private final JsonObject json;
4646

47+
public static final String NAN = Double.toString(Double.NaN);
48+
/**
49+
* Quoted version of {@link Double#NaN}. Needed for comparison when de-serializing,
50+
* as serialization of {@link Double#NaN} creates a quotes string.
51+
*/
52+
public static final String NAN_QUOTED = "\"" + NAN + "\"";
53+
public static final String POS_INF = Double.toString(Double.POSITIVE_INFINITY);
54+
/**
55+
* Quoted version of {@link Double#POSITIVE_INFINITY}. Needed for comparison when de-serializing,
56+
* as serialization of {@link Double#POSITIVE_INFINITY} creates a quotes string.
57+
*/
58+
public static final String POS_INF_QUOTED = "\"" + POS_INF + "\"";
59+
public static final String NEG_INF = Double.toString(Double.NEGATIVE_INFINITY);
60+
/**
61+
* Quoted version of {@link Double#NEGATIVE_INFINITY}. Needed for comparison when de-serializing,
62+
* as serialization of {@link Double#NEGATIVE_INFINITY} creates a quotes string.
63+
*/
64+
public static final String NEG_INF_QUOTED = "\"" + NEG_INF + "\"";
65+
4766
public VTypeJsonMapper(JsonObject json) {
4867
this.json = json;
4968
}

epics-vtype/vtype-json/src/main/java/org/epics/vtype/json/VTypeToJsonV1.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.epics.util.number.ULong;
1313
import org.epics.util.number.UShort;
1414
import org.epics.vtype.EnumDisplay;
15+
import org.epics.vtype.VDouble;
1516
import org.epics.vtype.VEnum;
1617
import org.epics.vtype.VNumber;
1718
import org.epics.vtype.VNumberArray;
@@ -83,6 +84,10 @@ static VNumber toVNumber(JsonObject json) {
8384
Number value;
8485
switch(mapper.getTypeName()) {
8586
case "VDouble":
87+
Double doubleValue = getDoubleFromJsonString(json.get("value").toString());
88+
if(doubleValue != null){
89+
return VDouble.of(doubleValue, mapper.getAlarm(), mapper.getTime(), mapper.getDisplay());
90+
}
8691
value = mapper.getJsonNumber("value").doubleValue();
8792
break;
8893
case "VFloat":
@@ -207,4 +212,28 @@ static JsonObject toJson(VEnum vEnum) {
207212
.addEnum(vEnum)
208213
.build();
209214
}
215+
216+
/**
217+
* Converts an input (JSON) string to a {@link Double}, taking into account the special cases
218+
* {@link Double#NaN}, {@link Double#POSITIVE_INFINITY} and {@link Double#NEGATIVE_INFINITY}.
219+
* If the input string is not parseable as a double (including the special cases), a
220+
* {@link NumberFormatException} is thrown.
221+
* @param valueAsString
222+
* @return A valid double number, otherwise
223+
* {@link Double#NaN}, {@link Double#POSITIVE_INFINITY} and {@link Double#NEGATIVE_INFINITY}.
224+
*/
225+
public static Double getDoubleFromJsonString(String valueAsString){
226+
if(VTypeJsonMapper.NAN_QUOTED.equals(valueAsString)){
227+
return Double.NaN;
228+
}
229+
else if(VTypeJsonMapper.POS_INF_QUOTED.equals(valueAsString)){
230+
return Double.POSITIVE_INFINITY;
231+
}
232+
else if(VTypeJsonMapper.NEG_INF_QUOTED.equals(valueAsString)){
233+
return Double.NEGATIVE_INFINITY;
234+
}
235+
else{
236+
return Double.parseDouble(valueAsString);
237+
}
238+
}
210239
}

epics-vtype/vtype-json/src/test/java/org/epics/vtype/json/VTypeToJsonTest.java

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import java.time.Instant;
1212
import java.util.Arrays;
1313
import java.util.Collections;
14+
import java.util.List;
1415
import javax.json.Json;
1516
import javax.json.JsonObject;
1617
import javax.json.JsonReader;
@@ -288,5 +289,70 @@ public void vByteArray1() {
288289
testDeserialization("VByteArray1", vByteArray1);
289290
testDeserialization("VByteArray1a", vByteArray1);
290291
}
291-
292+
293+
/**
294+
* Tests serialization and de-serialization of a Double.NaN. See {@link JsonVTypeBuilder#add} and
295+
* {@link VTypeToJsonV1#toVNumber}
296+
*/
297+
@Test
298+
public void testDoubleNaN(){
299+
VDouble vDouble = VDouble.of(Double.NaN, Alarm.none(), Time.of(Instant.EPOCH), Display.none());
300+
JsonObject jsonObject = VTypeToJson.toJson(vDouble);
301+
assertEquals("NaN", jsonObject.getString("value"));
302+
vDouble = (VDouble)VTypeToJson.toVType(jsonObject);
303+
assertTrue(Double.isNaN(vDouble.getValue()));
304+
}
305+
306+
@Test
307+
public void testDoublePositiveInfinity(){
308+
VDouble vDouble = VDouble.of(Double.POSITIVE_INFINITY, Alarm.none(), Time.of(Instant.EPOCH), Display.none());
309+
JsonObject jsonObject = VTypeToJson.toJson(vDouble);
310+
assertEquals(Double.toString(Double.POSITIVE_INFINITY), jsonObject.getString("value"));
311+
vDouble = (VDouble)VTypeToJson.toVType(jsonObject);
312+
assertTrue(vDouble.getValue().equals(Double.POSITIVE_INFINITY));
313+
}
314+
315+
@Test
316+
public void testDoubleNegativeInfinity(){
317+
VDouble vDouble = VDouble.of(Double.NEGATIVE_INFINITY, Alarm.none(), Time.of(Instant.EPOCH), Display.none());
318+
JsonObject jsonObject = VTypeToJson.toJson(vDouble);
319+
assertEquals(VTypeJsonMapper.NEG_INF, jsonObject.getString("value"));
320+
vDouble = (VDouble)VTypeToJson.toVType(jsonObject);
321+
assertTrue(vDouble.getValue().equals(Double.NEGATIVE_INFINITY));
322+
}
323+
324+
@Test
325+
public void testDoubleNaNInArray(){
326+
VDoubleArray vDoubleArray1 = VDoubleArray.of(ArrayDouble.of(0, Double.NaN, 2), Alarm.none(), Time.of(Instant.ofEpochSecond(0, 0)), Display.none());
327+
JsonObject jsonObject = VTypeToJson.toJson(vDoubleArray1);
328+
List valueObject = (List)jsonObject.get("value");
329+
assertEquals(VTypeJsonMapper.NAN_QUOTED, valueObject.get(1).toString());
330+
vDoubleArray1 = (VDoubleArray)VTypeToJson.toVType(jsonObject);
331+
assertTrue(Double.isNaN(vDoubleArray1.getData().getDouble(1)));
332+
}
333+
334+
@Test
335+
public void testDoublePositiveInfinityInArray(){
336+
VDoubleArray vDoubleArray1 = VDoubleArray.of(ArrayDouble.of(0, Double.POSITIVE_INFINITY, 2), Alarm.none(), Time.of(Instant.ofEpochSecond(0, 0)), Display.none());
337+
JsonObject jsonObject = VTypeToJson.toJson(vDoubleArray1);
338+
List valueObject = (List)jsonObject.get("value");
339+
assertEquals(VTypeJsonMapper.POS_INF_QUOTED, valueObject.get(1).toString());
340+
vDoubleArray1 = (VDoubleArray)VTypeToJson.toVType(jsonObject);
341+
assertTrue(Double.isInfinite(vDoubleArray1.getData().getDouble(1)));
342+
}
343+
344+
@Test
345+
public void testDoubleNegativeInfinityInArray(){
346+
VDoubleArray vDoubleArray1 = VDoubleArray.of(ArrayDouble.of(0, Double.NEGATIVE_INFINITY, 2), Alarm.none(), Time.of(Instant.ofEpochSecond(0, 0)), Display.none());
347+
JsonObject jsonObject = VTypeToJson.toJson(vDoubleArray1);
348+
List valueObject = (List)jsonObject.get("value");
349+
assertEquals(VTypeJsonMapper.NEG_INF_QUOTED, valueObject.get(1).toString());
350+
vDoubleArray1 = (VDoubleArray)VTypeToJson.toVType(jsonObject);
351+
assertTrue(Double.isInfinite(vDoubleArray1.getData().getDouble(1)));
352+
}
353+
354+
@Test(expected = NumberFormatException.class)
355+
public void testGetDoubleFromStringNonParsableValue(){
356+
VTypeToJsonV1.getDoubleFromJsonString("invalid");
357+
}
292358
}

0 commit comments

Comments
 (0)