Skip to content

Commit a0f1731

Browse files
committed
provide NaN and Infinity in JsonGenerator
Signed-off-by: Jorge Bescos Gascon <[email protected]>
1 parent 1820e42 commit a0f1731

File tree

7 files changed

+166
-50
lines changed

7 files changed

+166
-50
lines changed

impl/src/main/java/org/eclipse/parsson/JsonGeneratorFactoryImpl.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,22 +44,22 @@ class JsonGeneratorFactoryImpl implements JsonGeneratorFactory {
4444
@Override
4545
public JsonGenerator createGenerator(Writer writer) {
4646
return prettyPrinting
47-
? new JsonPrettyGeneratorImpl(writer, bufferPool)
48-
: new JsonGeneratorImpl(writer, bufferPool);
47+
? new JsonPrettyGeneratorImpl(writer, bufferPool, config)
48+
: new JsonGeneratorImpl(writer, bufferPool, config);
4949
}
5050

5151
@Override
5252
public JsonGenerator createGenerator(OutputStream out) {
5353
return prettyPrinting
54-
? new JsonPrettyGeneratorImpl(out, bufferPool)
55-
: new JsonGeneratorImpl(out, bufferPool);
54+
? new JsonPrettyGeneratorImpl(out, bufferPool, config)
55+
: new JsonGeneratorImpl(out, bufferPool, config);
5656
}
5757

5858
@Override
5959
public JsonGenerator createGenerator(OutputStream out, Charset charset) {
6060
return prettyPrinting
61-
? new JsonPrettyGeneratorImpl(out, charset, bufferPool)
62-
: new JsonGeneratorImpl(out, charset, bufferPool);
61+
? new JsonPrettyGeneratorImpl(out, charset, bufferPool, config)
62+
: new JsonGeneratorImpl(out, charset, bufferPool, config);
6363
}
6464

6565
@Override

impl/src/main/java/org/eclipse/parsson/JsonGeneratorImpl.java

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.eclipse.parsson;
1818

19+
import org.eclipse.parsson.JsonUtil.NormalNaNInfinite;
1920
import org.eclipse.parsson.api.BufferPool;
2021

2122
import jakarta.json.*;
@@ -80,6 +81,8 @@ private static enum Scope {
8081
IN_ARRAY
8182
}
8283

84+
private final boolean writeNanAsNulls;
85+
private final boolean writeNanAsStrings;
8386
private final BufferPool bufferPool;
8487
private final Writer writer;
8588
private Context currentContext = new Context(Scope.IN_NONE);
@@ -91,18 +94,22 @@ private static enum Scope {
9194
private final char buf[]; // capacity >= INT_MIN_VALUE_CHARS.length
9295
private int len = 0;
9396

94-
JsonGeneratorImpl(Writer writer, BufferPool bufferPool) {
97+
JsonGeneratorImpl(Writer writer, BufferPool bufferPool, Map<String, ?> config) {
9598
this.writer = writer;
9699
this.bufferPool = bufferPool;
97100
this.buf = bufferPool.take();
101+
Boolean nulls = (Boolean) config.get(JsonGenerator.WRITE_NAN_AS_NULLS);
102+
this.writeNanAsStrings = JsonUtil.getConfigValue(JsonGenerator.WRITE_NAN_AS_STRINGS, false, config);
103+
// The value of writeNanAsNulls is the opposite of writeNanAsStrings when writeNanAsNulls is not set
104+
this.writeNanAsNulls = nulls == null ? !writeNanAsStrings : nulls;
98105
}
99106

100-
JsonGeneratorImpl(OutputStream out, BufferPool bufferPool) {
101-
this(out, StandardCharsets.UTF_8, bufferPool);
107+
JsonGeneratorImpl(OutputStream out, BufferPool bufferPool, Map<String, ?> config) {
108+
this(out, StandardCharsets.UTF_8, bufferPool, config);
102109
}
103110

104-
JsonGeneratorImpl(OutputStream out, Charset encoding, BufferPool bufferPool) {
105-
this(new OutputStreamWriter(out, encoding), bufferPool);
111+
JsonGeneratorImpl(OutputStream out, Charset encoding, BufferPool bufferPool, Map<String, ?> config) {
112+
this(new OutputStreamWriter(out, encoding), bufferPool, config);
106113
}
107114

108115
@Override
@@ -184,11 +191,10 @@ public JsonGenerator write(String name, double value) {
184191
throw new JsonGenerationException(
185192
JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
186193
}
187-
if (Double.isInfinite(value) || Double.isNaN(value)) {
188-
throw new NumberFormatException(JsonMessages.GENERATOR_DOUBLE_INFINITE_NAN());
189-
}
194+
NormalNaNInfinite normalNaNInfinite = NormalNaNInfinite.get(value);
195+
Object result = normalNaNInfinite.processValue(writeNanAsNulls, writeNanAsStrings, value);
190196
writeName(name);
191-
writeString(String.valueOf(value));
197+
writeString(String.valueOf(result));
192198
return this;
193199
}
194200

@@ -382,10 +388,9 @@ public JsonGenerator write(long value) {
382388
@Override
383389
public JsonGenerator write(double value) {
384390
checkContextForValue();
385-
if (Double.isInfinite(value) || Double.isNaN(value)) {
386-
throw new NumberFormatException(JsonMessages.GENERATOR_DOUBLE_INFINITE_NAN());
387-
}
388-
writeValue(String.valueOf(value));
391+
NormalNaNInfinite normalNaNInfinite = NormalNaNInfinite.get(value);
392+
Object result = normalNaNInfinite.processValue(writeNanAsNulls, writeNanAsStrings, value);
393+
writeValue(String.valueOf(result));
389394
popFieldContext();
390395
return this;
391396
}

impl/src/main/java/org/eclipse/parsson/JsonPrettyGeneratorImpl.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,25 @@
2222
import java.io.OutputStream;
2323
import java.io.Writer;
2424
import java.nio.charset.Charset;
25+
import java.util.Map;
2526

2627
/**
2728
* @author Jitendra Kotamraju
2829
*/
29-
public class JsonPrettyGeneratorImpl extends JsonGeneratorImpl {
30+
class JsonPrettyGeneratorImpl extends JsonGeneratorImpl {
3031
private int indentLevel;
3132
private static final String INDENT = " ";
3233

33-
public JsonPrettyGeneratorImpl(Writer writer, BufferPool bufferPool) {
34-
super(writer, bufferPool);
34+
JsonPrettyGeneratorImpl(Writer writer, BufferPool bufferPool, Map<String, ?> config) {
35+
super(writer, bufferPool, config);
3536
}
3637

37-
public JsonPrettyGeneratorImpl(OutputStream out, BufferPool bufferPool) {
38-
super(out, bufferPool);
38+
JsonPrettyGeneratorImpl(OutputStream out, BufferPool bufferPool, Map<String, ?> config) {
39+
super(out, bufferPool, config);
3940
}
4041

41-
public JsonPrettyGeneratorImpl(OutputStream out, Charset encoding, BufferPool bufferPool) {
42-
super(out, encoding, bufferPool);
42+
JsonPrettyGeneratorImpl(OutputStream out, Charset encoding, BufferPool bufferPool, Map<String, ?> config) {
43+
super(out, encoding, bufferPool, config);
4344
}
4445

4546
@Override

impl/src/main/java/org/eclipse/parsson/JsonProviderImpl.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,12 @@ public class JsonProviderImpl extends JsonProvider {
4646

4747
@Override
4848
public JsonGenerator createGenerator(Writer writer) {
49-
return new JsonGeneratorImpl(writer, bufferPool);
49+
return new JsonGeneratorImpl(writer, bufferPool, Collections.emptyMap());
5050
}
5151

5252
@Override
5353
public JsonGenerator createGenerator(OutputStream out) {
54-
return new JsonGeneratorImpl(out, bufferPool);
54+
return new JsonGeneratorImpl(out, bufferPool, Collections.emptyMap());
5555
}
5656

5757
@Override
@@ -87,15 +87,14 @@ public JsonGeneratorFactory createGeneratorFactory(Map<String, ?> config) {
8787
pool = bufferPool;
8888
} else {
8989
providerConfig = new HashMap<>();
90-
if (prettyPrinting=JsonProviderImpl.isPrettyPrintingEnabled(config)) {
91-
providerConfig.put(JsonGenerator.PRETTY_PRINTING, true);
92-
}
90+
prettyPrinting = JsonProviderImpl.isPrettyPrintingEnabled(config);
9391
pool = (BufferPool)config.get(BufferPool.class.getName());
9492
if (pool != null) {
9593
providerConfig.put(BufferPool.class.getName(), pool);
9694
} else {
9795
pool = bufferPool;
9896
}
97+
providerConfig.putAll(config);
9998
providerConfig = Collections.unmodifiableMap(providerConfig);
10099
}
101100

impl/src/main/java/org/eclipse/parsson/JsonUtil.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package org.eclipse.parsson;
1818

1919
import java.io.StringReader;
20+
import java.util.Map;
21+
2022
import jakarta.json.JsonReader;
2123
import jakarta.json.JsonValue;
2224
import jakarta.json.stream.JsonParsingException;
@@ -81,5 +83,50 @@ public static JsonValue toJson(String jsonString) {
8183
reader.close();
8284
return value;
8385
}
86+
87+
static boolean getConfigValue(String key, boolean defaultValue, Map<String, ?> config) {
88+
Object value = config.get(key);
89+
if (value instanceof Boolean) {
90+
return (boolean) value;
91+
}
92+
return defaultValue;
93+
}
94+
95+
static enum NormalNaNInfinite {
96+
NORMAL(null), NAN("\"NaN\""), ININITE_PLUS("\"+Infinity\""), INFINITE_MINUS("\"-Infinity\"");
97+
98+
private final String strVal;
99+
100+
private NormalNaNInfinite(String strVal) {
101+
this.strVal = strVal;
102+
}
103+
104+
Object processValue(boolean writeNanAsNulls, boolean writeNanAsStrings, double value) {
105+
if (this == NormalNaNInfinite.NORMAL) {
106+
return value;
107+
} else {
108+
if (writeNanAsNulls) {
109+
return null;
110+
} else if (writeNanAsStrings) {
111+
return strVal;
112+
} else {
113+
throw new NumberFormatException(JsonMessages.GENERATOR_DOUBLE_INFINITE_NAN());
114+
}
115+
}
116+
}
117+
118+
static NormalNaNInfinite get(double value) {
119+
if (Double.isNaN(value)) {
120+
return NAN;
121+
} else if (Double.isInfinite(value)) {
122+
if (Double.compare(value, 0.0) < 0) {
123+
return INFINITE_MINUS;
124+
} else {
125+
return ININITE_PLUS;
126+
}
127+
}
128+
return NORMAL;
129+
}
130+
}
84131
}
85132

impl/src/main/java/org/eclipse/parsson/JsonWriterImpl.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,23 @@
1616

1717
package org.eclipse.parsson;
1818

19-
import org.eclipse.parsson.api.BufferPool;
20-
21-
import jakarta.json.*;
2219
import java.io.FilterOutputStream;
2320
import java.io.IOException;
2421
import java.io.OutputStream;
2522
import java.io.Writer;
2623
import java.nio.charset.Charset;
2724
import java.nio.charset.StandardCharsets;
25+
import java.util.Collections;
2826
import java.util.Map;
2927

28+
import org.eclipse.parsson.api.BufferPool;
29+
30+
import jakarta.json.JsonArray;
31+
import jakarta.json.JsonObject;
32+
import jakarta.json.JsonStructure;
33+
import jakarta.json.JsonValue;
34+
import jakarta.json.JsonWriter;
35+
3036
/**
3137
* JsonWriter impl using generator.
3238
*
@@ -44,8 +50,8 @@ class JsonWriterImpl implements JsonWriter {
4450

4551
JsonWriterImpl(Writer writer, boolean prettyPrinting, BufferPool bufferPool) {
4652
generator = prettyPrinting
47-
? new JsonPrettyGeneratorImpl(writer, bufferPool)
48-
: new JsonGeneratorImpl(writer, bufferPool);
53+
? new JsonPrettyGeneratorImpl(writer, bufferPool, Collections.emptyMap())
54+
: new JsonGeneratorImpl(writer, bufferPool, Collections.emptyMap());
4955
os = null;
5056
}
5157

@@ -63,8 +69,8 @@ class JsonWriterImpl implements JsonWriter {
6369
// written without actually flushing the stream.
6470
this.os = new NoFlushOutputStream(out);
6571
generator = prettyPrinting
66-
? new JsonPrettyGeneratorImpl(os, charset, bufferPool)
67-
: new JsonGeneratorImpl(os, charset, bufferPool);
72+
? new JsonPrettyGeneratorImpl(os, charset, bufferPool, Collections.emptyMap())
73+
: new JsonGeneratorImpl(os, charset, bufferPool, Collections.emptyMap());
6874
}
6975

7076
@Override

impl/src/test/java/org/eclipse/parsson/tests/JsonGeneratorTest.java

Lines changed: 70 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -369,21 +369,18 @@ public void testGeneratorArrayDouble() throws Exception {
369369
generator.writeStartArray();
370370
try {
371371
generator.write(Double.NaN);
372-
fail("JsonGenerator.write(Double.NaN) should produce NumberFormatException");
373372
} catch (NumberFormatException ne) {
374-
// expected
373+
fail("JsonGenerator.write(Double.NaN) should NOT produce NumberFormatException");
375374
}
376375
try {
377376
generator.write(Double.POSITIVE_INFINITY);
378-
fail("JsonGenerator.write(Double.POSITIVE_INIFINITY) should produce NumberFormatException");
379377
} catch (NumberFormatException ne) {
380-
// expected
378+
fail("JsonGenerator.write(Double.POSITIVE_INIFINITY) should NOT produce NumberFormatException");
381379
}
382380
try {
383381
generator.write(Double.NEGATIVE_INFINITY);
384-
fail("JsonGenerator.write(Double.NEGATIVE_INIFINITY) should produce NumberFormatException");
385382
} catch (NumberFormatException ne) {
386-
// expected
383+
fail("JsonGenerator.write(Double.NEGATIVE_INIFINITY) should NOT produce NumberFormatException");
387384
}
388385
generator.writeEnd();
389386
generator.close();
@@ -395,21 +392,18 @@ public void testGeneratorObjectDouble() throws Exception {
395392
generator.writeStartObject();
396393
try {
397394
generator.write("foo", Double.NaN);
398-
fail("JsonGenerator.write(String, Double.NaN) should produce NumberFormatException");
399395
} catch (NumberFormatException ne) {
400-
// expected
396+
fail("JsonGenerator.write(String, Double.NaN) should NOT produce NumberFormatException");
401397
}
402398
try {
403399
generator.write("foo", Double.POSITIVE_INFINITY);
404-
fail("JsonGenerator.write(String, Double.POSITIVE_INIFINITY) should produce NumberFormatException");
405400
} catch (NumberFormatException ne) {
406-
// expected
401+
fail("JsonGenerator.write(String, Double.POSITIVE_INIFINITY) should NOT produce NumberFormatException");
407402
}
408403
try {
409404
generator.write("foo", Double.NEGATIVE_INFINITY);
410-
fail("JsonGenerator.write(String, Double.NEGATIVE_INIFINITY) should produce NumberFormatException");
411405
} catch (NumberFormatException ne) {
412-
// expected
406+
fail("JsonGenerator.write(String, Double.NEGATIVE_INIFINITY) should NOT produce NumberFormatException");
413407
}
414408
generator.writeEnd();
415409
generator.close();
@@ -542,4 +536,68 @@ public void testFlush() throws Exception {
542536
assertEquals("{}", baos.toString("UTF-8"));
543537
}
544538

539+
private String nameValueNanInfinity(Map<String, Object> config) {
540+
JsonGeneratorFactory generatorFactory = Json.createGeneratorFactory(config);
541+
StringWriter writer = new StringWriter();
542+
JsonGenerator generator = generatorFactory.createGenerator(writer);
543+
generator
544+
.writeStartObject()
545+
.write("val1", Double.NaN)
546+
.write("val2", 1.0)
547+
.write("val3", 0.0)
548+
.write("val4", Double.POSITIVE_INFINITY)
549+
.write("val5", Double.NEGATIVE_INFINITY)
550+
.writeEnd().close();
551+
return writer.toString();
552+
}
553+
554+
private String valueNanInfinity(Map<String, Object> config) {
555+
JsonGeneratorFactory generatorFactory = Json.createGeneratorFactory(config);
556+
StringWriter writer = new StringWriter();
557+
JsonGenerator generator = generatorFactory.createGenerator(writer);
558+
generator.write(Double.NaN).close();
559+
return writer.toString();
560+
}
561+
562+
public void testNanInfinityDefault() {
563+
Map<String, Object> config = new HashMap<>();
564+
assertEquals("{\"val1\":null,\"val2\":1.0,\"val3\":0.0,\"val4\":null,\"val5\":null}", nameValueNanInfinity(config));
565+
assertEquals("null", valueNanInfinity(config));
566+
}
567+
568+
public void testNanInfinityWriteNanAsNull() {
569+
Map<String, Object> config = new HashMap<>();
570+
config.put(JsonGenerator.WRITE_NAN_AS_NULLS, true);
571+
assertEquals("{\"val1\":null,\"val2\":1.0,\"val3\":0.0,\"val4\":null,\"val5\":null}", nameValueNanInfinity(config));
572+
assertEquals("null", valueNanInfinity(config));
573+
}
574+
575+
public void testNanInfinityWriteNanAsString() {
576+
Map<String, Object> config = new HashMap<>();
577+
config.put(JsonGenerator.WRITE_NAN_AS_STRINGS, true);
578+
assertEquals("{\"val1\":\"NaN\",\"val2\":1.0,\"val3\":0.0,\"val4\":\"+Infinity\",\"val5\":\"-Infinity\"}", nameValueNanInfinity(config));
579+
assertEquals("\"NaN\"", valueNanInfinity(config));
580+
}
581+
582+
public void testNanInfinityBothFalse() {
583+
Map<String, Object> config = new HashMap<>();
584+
config.put(JsonGenerator.WRITE_NAN_AS_STRINGS, false);
585+
config.put(JsonGenerator.WRITE_NAN_AS_NULLS, false);
586+
try {
587+
nameValueNanInfinity(config);
588+
fail("Expected a failure");
589+
} catch (NumberFormatException e) {}
590+
try {
591+
valueNanInfinity(config);
592+
fail("Expected a failure");
593+
} catch (NumberFormatException e) {}
594+
}
595+
596+
public void testNanInfinityBothTrue() {
597+
Map<String, Object> config = new HashMap<>();
598+
config.put(JsonGenerator.WRITE_NAN_AS_STRINGS, true);
599+
config.put(JsonGenerator.WRITE_NAN_AS_NULLS, true);
600+
assertEquals("{\"val1\":null,\"val2\":1.0,\"val3\":0.0,\"val4\":null,\"val5\":null}", nameValueNanInfinity(config));
601+
assertEquals("null", valueNanInfinity(config));
602+
}
545603
}

0 commit comments

Comments
 (0)