Skip to content
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

provide "NaN" and "Infinity" when (de)serializing Java Numbers #32

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ public int hashCode() {
@Override
public String toString() {
StringWriter sw = new StringWriter();
try (JsonWriter jw = new JsonWriterImpl(sw, bufferPool)) {
try (JsonWriter jw = new JsonWriterImpl(sw, bufferPool, Collections.emptyMap())) {
jw.write(this);
}
return sw.toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,22 +44,22 @@ class JsonGeneratorFactoryImpl implements JsonGeneratorFactory {
@Override
public JsonGenerator createGenerator(Writer writer) {
return prettyPrinting
? new JsonPrettyGeneratorImpl(writer, bufferPool)
: new JsonGeneratorImpl(writer, bufferPool);
? new JsonPrettyGeneratorImpl(writer, bufferPool, config)
: new JsonGeneratorImpl(writer, bufferPool, config);
}

@Override
public JsonGenerator createGenerator(OutputStream out) {
return prettyPrinting
? new JsonPrettyGeneratorImpl(out, bufferPool)
: new JsonGeneratorImpl(out, bufferPool);
? new JsonPrettyGeneratorImpl(out, bufferPool, config)
: new JsonGeneratorImpl(out, bufferPool, config);
}

@Override
public JsonGenerator createGenerator(OutputStream out, Charset charset) {
return prettyPrinting
? new JsonPrettyGeneratorImpl(out, charset, bufferPool)
: new JsonGeneratorImpl(out, charset, bufferPool);
? new JsonPrettyGeneratorImpl(out, charset, bufferPool, config)
: new JsonGeneratorImpl(out, charset, bufferPool, config);
}

@Override
Expand Down
58 changes: 44 additions & 14 deletions impl/src/main/java/org/eclipse/parsson/JsonGeneratorImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package org.eclipse.parsson;

import org.eclipse.parsson.JsonNumberImpl.JsonNaNInfiniteNumber;
import org.eclipse.parsson.JsonUtil.NaNInfinite;
import org.eclipse.parsson.api.BufferPool;

import jakarta.json.*;
Expand Down Expand Up @@ -80,6 +82,8 @@ private static enum Scope {
IN_ARRAY
}

private final boolean writeNanAsNulls;
private final boolean writeNanAsStrings;
private final BufferPool bufferPool;
private final Writer writer;
private Context currentContext = new Context(Scope.IN_NONE);
Expand All @@ -91,18 +95,22 @@ private static enum Scope {
private final char buf[]; // capacity >= INT_MIN_VALUE_CHARS.length
private int len = 0;

JsonGeneratorImpl(Writer writer, BufferPool bufferPool) {
JsonGeneratorImpl(Writer writer, BufferPool bufferPool, Map<String, ?> config) {
this.writer = writer;
this.bufferPool = bufferPool;
this.buf = bufferPool.take();
Boolean nulls = (Boolean) config.get(JsonGenerator.WRITE_NAN_AS_NULLS);
this.writeNanAsStrings = JsonUtil.getConfigValue(JsonGenerator.WRITE_NAN_AS_STRINGS, false, config);
// The value of writeNanAsNulls is the opposite of writeNanAsStrings when writeNanAsNulls is not set
this.writeNanAsNulls = nulls == null ? !writeNanAsStrings : nulls;
}

JsonGeneratorImpl(OutputStream out, BufferPool bufferPool) {
this(out, StandardCharsets.UTF_8, bufferPool);
JsonGeneratorImpl(OutputStream out, BufferPool bufferPool, Map<String, ?> config) {
this(out, StandardCharsets.UTF_8, bufferPool, config);
}

JsonGeneratorImpl(OutputStream out, Charset encoding, BufferPool bufferPool) {
this(new OutputStreamWriter(out, encoding), bufferPool);
JsonGeneratorImpl(OutputStream out, Charset encoding, BufferPool bufferPool, Map<String, ?> config) {
this(new OutputStreamWriter(out, encoding), bufferPool, config);
}

@Override
Expand Down Expand Up @@ -184,11 +192,10 @@ public JsonGenerator write(String name, double value) {
throw new JsonGenerationException(
JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
}
if (Double.isInfinite(value) || Double.isNaN(value)) {
throw new NumberFormatException(JsonMessages.GENERATOR_DOUBLE_INFINITE_NAN());
}
NaNInfinite nanInfinite = NaNInfinite.get(value);
Object result = nanInfinite == null ? value : nanInfinite.processValue(writeNanAsNulls, writeNanAsStrings, value);
writeName(name);
writeString(String.valueOf(value));
writeString(String.valueOf(result));
return this;
}

Expand Down Expand Up @@ -263,7 +270,17 @@ public JsonGenerator write(JsonValue value) {
break;
case NUMBER:
JsonNumber number = (JsonNumber)value;
writeValue(number.toString());
if (number instanceof JsonNaNInfiniteNumber) {
JsonNaNInfiniteNumber nanInfinite = (JsonNaNInfiniteNumber) number;
String val = nanInfinite.naNInfinite().processValue(writeNanAsNulls, writeNanAsStrings, nanInfinite.doubleValue());
if (val == null) {
writeNull();
} else {
writeValue(val);
}
} else {
writeValue(number.toString());
}
popFieldContext();
break;
case TRUE:
Expand Down Expand Up @@ -337,7 +354,17 @@ public JsonGenerator write(String name, JsonValue value) {
break;
case NUMBER:
JsonNumber number = (JsonNumber)value;
writeValue(name, number.toString());
if (number instanceof JsonNaNInfiniteNumber) {
JsonNaNInfiniteNumber nanInfinite = (JsonNaNInfiniteNumber) number;
String val = nanInfinite.naNInfinite().processValue(writeNanAsNulls, writeNanAsStrings, nanInfinite.doubleValue());
if (val == null) {
writeNull(name);
} else {
writeValue(name, val);
}
} else {
writeValue(name, number.toString());
}
break;
case TRUE:
write(name, true);
Expand Down Expand Up @@ -382,10 +409,13 @@ public JsonGenerator write(long value) {
@Override
public JsonGenerator write(double value) {
checkContextForValue();
if (Double.isInfinite(value) || Double.isNaN(value)) {
throw new NumberFormatException(JsonMessages.GENERATOR_DOUBLE_INFINITE_NAN());
NaNInfinite nanInfinite = NaNInfinite.get(value);
if (nanInfinite == null) {
writeValue(String.valueOf(value));
} else {
Object result = nanInfinite.processValue(writeNanAsNulls, writeNanAsStrings, value);
writeValue(String.valueOf(result));
}
writeValue(String.valueOf(value));
popFieldContext();
return this;
}
Expand Down
86 changes: 83 additions & 3 deletions impl/src/main/java/org/eclipse/parsson/JsonNumberImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import java.math.BigDecimal;
import java.math.BigInteger;

import org.eclipse.parsson.JsonUtil.NaNInfinite;

/**
* JsonNumber impl. Subclasses provide optimized implementations
* when backed by int, long, BigDecimal
Expand All @@ -43,15 +45,42 @@ static JsonNumber getJsonNumber(BigInteger value) {
}

static JsonNumber getJsonNumber(double value) {
//bigDecimal = new BigDecimal(value);
// This is the preferred way to convert double to BigDecimal
return new JsonBigDecimalNumber(BigDecimal.valueOf(value));
NaNInfinite nanInfinite = NaNInfinite.get(value);
if (nanInfinite == null) {
//bigDecimal = new BigDecimal(value);
// This is the preferred way to convert double to BigDecimal
return new JsonBigDecimalNumber(BigDecimal.valueOf(value));
} else {
return JsonNaNInfiniteNumber.get(nanInfinite);
}
}

static JsonNumber getJsonNumber(BigDecimal value) {
return new JsonBigDecimalNumber(value);
}

static final class JsonNullNumber extends JsonNumberImpl {

static final JsonNullNumber NULL = new JsonNullNumber();

private JsonNullNumber() {}

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

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

@Override
public BigDecimal bigDecimalValue() {
throw new NullPointerException("Value is null");
}
}

// Optimized JsonNumber impl for int numbers.
private static final class JsonIntNumber extends JsonNumberImpl {
private final int num;
Expand Down Expand Up @@ -175,6 +204,57 @@ public String toString() {

}

static final class JsonNaNInfiniteNumber extends JsonNumberImpl {

private static final JsonNaNInfiniteNumber NAN = new JsonNaNInfiniteNumber(Double.NaN, NaNInfinite.NAN);
private static final JsonNaNInfiniteNumber POSITIVE_INFINITY = new JsonNaNInfiniteNumber(Double.POSITIVE_INFINITY, NaNInfinite.POSITIVE_INFINITY);
private static final JsonNaNInfiniteNumber NEGATIVE_INFINITY = new JsonNaNInfiniteNumber(Double.NEGATIVE_INFINITY, NaNInfinite.NEGATIVE_INFINITY);
private final double num;
private final NaNInfinite naNInfinite;

private JsonNaNInfiniteNumber(double num, NaNInfinite naNInfinite) {
this.num = num;
this.naNInfinite = naNInfinite;
}

NaNInfinite naNInfinite() {
return naNInfinite;
}

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

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

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

@Override
public BigDecimal bigDecimalValue() {
// Every other method in this class that is not overridden will fail because of this exception
throw new UnsupportedOperationException("Value is " + num);
}

static JsonNaNInfiniteNumber get(NaNInfinite nanInfinite) {
if (nanInfinite == NaNInfinite.NAN) {
return NAN;
} else if (nanInfinite == NaNInfinite.NEGATIVE_INFINITY) {
return NEGATIVE_INFINITY;
} else if (nanInfinite == NaNInfinite.POSITIVE_INFINITY) {
return POSITIVE_INFINITY;
} else {
throw new IllegalArgumentException(nanInfinite + " is not known");
}
}
}

// JsonNumber impl using BigDecimal numbers.
private static final class JsonBigDecimalNumber extends JsonNumberImpl {
private final BigDecimal bigDecimal;
Expand Down
38 changes: 32 additions & 6 deletions impl/src/main/java/org/eclipse/parsson/JsonObjectBuilderImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,29 @@

package org.eclipse.parsson;

import org.eclipse.parsson.api.BufferPool;

import jakarta.json.*;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import java.util.AbstractMap;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import org.eclipse.parsson.JsonNumberImpl.JsonNaNInfiniteNumber;
import org.eclipse.parsson.JsonNumberImpl.JsonNullNumber;
import org.eclipse.parsson.JsonUtil.NaNInfinite;
import org.eclipse.parsson.api.BufferPool;

import jakarta.json.JsonArray;
import jakarta.json.JsonArrayBuilder;
import jakarta.json.JsonNumber;
import jakarta.json.JsonObject;
import jakarta.json.JsonObjectBuilder;
import jakarta.json.JsonString;
import jakarta.json.JsonValue;
import jakarta.json.JsonWriter;

/**
* JsonObjectBuilder implementation
Expand Down Expand Up @@ -246,7 +262,17 @@ public JsonObject getJsonObject(String name) {

@Override
public JsonNumber getJsonNumber(String name) {
return (JsonNumber)get(name);
JsonValue value = get(name);
if (value.getValueType() == ValueType.NULL) {
return JsonNullNumber.NULL;
} else {
NaNInfinite nanInfinite = null;
if (value.getValueType() == ValueType.STRING && (nanInfinite = NaNInfinite.get(value.toString())) != null) {
return JsonNaNInfiniteNumber.get(nanInfinite);
} else {
return (JsonNumber)get(name);
}
}
}

@Override
Expand Down Expand Up @@ -336,7 +362,7 @@ public int hashCode() {
@Override
public String toString() {
StringWriter sw = new StringWriter();
try (JsonWriter jw = new JsonWriterImpl(sw, bufferPool)) {
try (JsonWriter jw = new JsonWriterImpl(sw, bufferPool, Collections.emptyMap())) {
jw.write(this);
}
return sw.toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,25 @@
import java.io.OutputStream;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.Map;

/**
* @author Jitendra Kotamraju
*/
public class JsonPrettyGeneratorImpl extends JsonGeneratorImpl {
class JsonPrettyGeneratorImpl extends JsonGeneratorImpl {
private int indentLevel;
private static final String INDENT = " ";

public JsonPrettyGeneratorImpl(Writer writer, BufferPool bufferPool) {
super(writer, bufferPool);
JsonPrettyGeneratorImpl(Writer writer, BufferPool bufferPool, Map<String, ?> config) {
super(writer, bufferPool, config);
}

public JsonPrettyGeneratorImpl(OutputStream out, BufferPool bufferPool) {
super(out, bufferPool);
JsonPrettyGeneratorImpl(OutputStream out, BufferPool bufferPool, Map<String, ?> config) {
super(out, bufferPool, config);
}

public JsonPrettyGeneratorImpl(OutputStream out, Charset encoding, BufferPool bufferPool) {
super(out, encoding, bufferPool);
JsonPrettyGeneratorImpl(OutputStream out, Charset encoding, BufferPool bufferPool, Map<String, ?> config) {
super(out, encoding, bufferPool, config);
}

@Override
Expand Down
Loading