Skip to content

XML serialization of floating-point infinity is incompatible with JAXB and XML Schema #643

@ahcodedthat

Description

@ahcodedthat

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    2.17Issues planned at earliest for 2.17

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions