-
-
Notifications
You must be signed in to change notification settings - Fork 230
Description
As of version 2.16.1, infinite values of float
and double
are serialized in a way that is incompatible with the XML Schema definition and JAXB. Specifically, jackson-dataformat-xml serializes these values as the strings Infinity
or -Infinity
. XML Schema, however, says they should be serialized as INF
or -INF
, and that is what JAXB does.
Example program (click to show)
package org.example;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import javax.xml.bind.JAXB;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
public class Main {
public static void main(String[] args) throws IOException {
ExampleObject original, deserialized;
String serialized;
original = new ExampleObject();
original.x = Double.POSITIVE_INFINITY;
original.y = Double.NEGATIVE_INFINITY;
original.z = Double.NaN;
original.fx = Float.POSITIVE_INFINITY;
original.fy = Float.NEGATIVE_INFINITY;
original.fz = Float.NaN;
System.out.println("--- Jackson serialization ---");
serialized = serializeWithJackson(original);
System.out.println(serialized);
System.out.println("--- Jackson deserialization ---");
deserialized = deserializeWithJackson(serialized);
System.out.println(deserialized);
System.out.println("--- JAXB serialization ---");
serialized = serializeWithJaxb(original);
System.out.println(serialized);
System.out.println("--- JAXB deserialization ---");
deserialized = deserializeWithJaxb(serialized);
System.out.println(deserialized);
System.out.println("--- serialized with JAXB, deserialized with Jackson ---");
deserialized = deserializeWithJackson(serialized);
System.out.println(deserialized);
System.out.println("--- serialized with Jackson, deserialized with JAXB ---");
serialized = serializeWithJackson(original);
deserialized = deserializeWithJaxb(serialized);
System.out.println(deserialized);
}
private static String serializeWithJackson(ExampleObject object) throws IOException {
var buf = new StringWriter();
new XmlMapper().writeValue(buf, object);
return buf.toString();
}
private static ExampleObject deserializeWithJackson(String xml) throws JsonProcessingException {
return new XmlMapper().readValue(xml, ExampleObject.class);
}
private static String serializeWithJaxb(ExampleObject object) {
var buf = new StringWriter();
JAXB.marshal(object, buf);
return buf.toString();
}
private static ExampleObject deserializeWithJaxb(String xml) {
return JAXB.unmarshal(new StringReader(xml), ExampleObject.class);
}
}
@XmlRootElement(name = "example")
class ExampleObject {
@XmlElement
public double x, y, z;
@XmlElement
public float fx, fy, fz;
@Override
public String toString() {
return String.format("x=%f y=%f z=%f fx=%f fy=%f fz=%f", x, y, z, fx, fy, fz);
}
}
Maven POM for example program (click to show)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>jackson-xml-double</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.16.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.16.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.16.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.3</version>
</dependency>
</dependencies>
</project>
Output from example program (click to show)
--- Jackson serialization ---
<ExampleObject><x>Infinity</x><y>-Infinity</y><z>NaN</z><fx>Infinity</fx><fy>-Infinity</fy><fz>NaN</fz></ExampleObject>
--- Jackson deserialization ---
x=Infinity y=-Infinity z=NaN fx=Infinity fy=-Infinity fz=NaN
--- JAXB serialization ---
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<example>
<x>INF</x>
<y>-INF</y>
<z>NaN</z>
<fx>INF</fx>
<fy>-INF</fy>
<fz>NaN</fz>
</example>
--- JAXB deserialization ---
x=Infinity y=-Infinity z=NaN fx=Infinity fy=-Infinity fz=NaN
--- serialized with JAXB, deserialized with Jackson ---
x=Infinity y=-Infinity z=NaN fx=Infinity fy=-Infinity fz=NaN
--- serialized with Jackson, deserialized with JAXB ---
x=0.000000 y=0.000000 z=NaN fx=0.000000 fy=0.000000 fz=NaN
As the example program's output shows, Jackson understands both its own format and the XML Schema format for floating-point infinity. JAXB, however, understands only the XML Schema format, and fails to parse Jackson's format.
The problem seems to be that jackson-dataformat-xml calls TypedXMLStreamWriter
methods to serialize floating-point values, which ultimately uses NumberUtil.write{Float,Double}
from StAX2, which in turn uses java.lang.String.valueOf
to serialize the number, without any special handling of infinity.
Deserialization of XML Schema-formatted numbers seems to work correctly. Only serialization has an issue.
This issue only affects positive and negative infinity. java.lang.String.valueOf
differs from XML Schema only in how it represents infinity; it uses the same format as XML Schema for NaN and finite values.