diff --git a/toml/src/main/java/com/fasterxml/jackson/dataformat/toml/TomlGenerator.java b/toml/src/main/java/com/fasterxml/jackson/dataformat/toml/TomlGenerator.java index e9a1ba70..019d13c1 100644 --- a/toml/src/main/java/com/fasterxml/jackson/dataformat/toml/TomlGenerator.java +++ b/toml/src/main/java/com/fasterxml/jackson/dataformat/toml/TomlGenerator.java @@ -545,17 +545,37 @@ public void writeNumber(BigInteger v) throws IOException { @Override public void writeNumber(double d) throws IOException { _verifyValueWrite("write number"); - _writeRaw(String.valueOf(d)); + _writeRaw(_nonFiniteTomlToken(d, String.valueOf(d))); writeValueEnd(); } @Override public void writeNumber(float f) throws IOException { _verifyValueWrite("write number"); - _writeRaw(String.valueOf(f)); + _writeRaw(_nonFiniteTomlToken(f, String.valueOf(f))); writeValueEnd(); } + /** + * Maps a non-finite floating-point value to the TOML float token + * ({@code nan}, {@code inf} or {@code -inf}); finite values are written + * using the supplied Java text form. {@code String.valueOf(...)} would + * otherwise emit {@code NaN} / {@code Infinity} / {@code -Infinity}, which + * are not valid TOML and cannot be read back by the parser. + */ + private static String _nonFiniteTomlToken(double d, String finiteForm) { + if (Double.isNaN(d)) { + return "nan"; + } + if (d == Double.POSITIVE_INFINITY) { + return "inf"; + } + if (d == Double.NEGATIVE_INFINITY) { + return "-inf"; + } + return finiteForm; + } + @Override public void writeNumber(BigDecimal dec) throws IOException { if (dec == null) { diff --git a/toml/src/test/java/com/fasterxml/jackson/dataformat/toml/TomlGeneratorTest.java b/toml/src/test/java/com/fasterxml/jackson/dataformat/toml/TomlGeneratorTest.java index 0fc58623..792d47c5 100644 --- a/toml/src/test/java/com/fasterxml/jackson/dataformat/toml/TomlGeneratorTest.java +++ b/toml/src/test/java/com/fasterxml/jackson/dataformat/toml/TomlGeneratorTest.java @@ -6,10 +6,12 @@ import org.junit.jupiter.api.Test; import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; public class TomlGeneratorTest extends TomlMapperTestBase { @Test @@ -48,6 +50,61 @@ public void floats() throws IOException { assertEquals("abc = 1.23\n", w.toString()); } + // [dataformats-text#696]: non-finite floats must be written as the TOML + // tokens `nan` / `inf` / `-inf` (not Java's `NaN` / `Infinity`) so the + // writer's own output can be parsed back. + @Test + public void nonFiniteDoubles() throws IOException { + assertEquals("abc = nan\n", _writeDouble(Double.NaN)); + assertEquals("abc = inf\n", _writeDouble(Double.POSITIVE_INFINITY)); + assertEquals("abc = -inf\n", _writeDouble(Double.NEGATIVE_INFINITY)); + } + + @Test + public void nonFiniteFloats() throws IOException { + assertEquals("abc = nan\n", _writeFloat(Float.NaN)); + assertEquals("abc = inf\n", _writeFloat(Float.POSITIVE_INFINITY)); + assertEquals("abc = -inf\n", _writeFloat(Float.NEGATIVE_INFINITY)); + } + + // Written non-finite values must round-trip through the same mapper. + @Test + public void nonFiniteRoundTrip() throws IOException { + TomlMapper mapper = newTomlMapper(); + for (double d : new double[] { + Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY }) { + String toml = mapper.writeValueAsString(java.util.Collections.singletonMap("x", d)); + JsonNode node = mapper.readTree(toml).get("x"); + if (Double.isNaN(d)) { + assertTrue(Double.isNaN(node.doubleValue()), "expected NaN from: " + toml); + } else { + assertEquals(d, node.doubleValue(), 0.0, "round-trip failed for: " + toml); + } + } + } + + private String _writeDouble(double d) throws IOException { + StringWriter w = new StringWriter(); + try (JsonGenerator generator = newTomlMapper().createGenerator(w)) { + generator.writeStartObject(); + generator.writeFieldName("abc"); + generator.writeNumber(d); + generator.writeEndObject(); + } + return w.toString(); + } + + private String _writeFloat(float f) throws IOException { + StringWriter w = new StringWriter(); + try (JsonGenerator generator = newTomlMapper().createGenerator(w)) { + generator.writeStartObject(); + generator.writeFieldName("abc"); + generator.writeNumber(f); + generator.writeEndObject(); + } + return w.toString(); + } + @Test public void stringNormal() throws IOException { StringWriter w = new StringWriter();