Skip to content

Fix #643: Add ToXmlGenerator.Feature or allowing XML Schema/JAXB compatible Infinity representation #644

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

Merged
merged 2 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -20,6 +20,7 @@
import com.fasterxml.jackson.core.json.JsonWriteContext;
import com.fasterxml.jackson.core.util.JacksonFeatureSet;
import com.fasterxml.jackson.dataformat.xml.XmlPrettyPrinter;
import com.fasterxml.jackson.dataformat.xml.deser.FromXmlParser;
import com.fasterxml.jackson.dataformat.xml.util.DefaultXmlPrettyPrinter;
import com.fasterxml.jackson.dataformat.xml.util.StaxUtil;

Expand Down Expand Up @@ -106,6 +107,34 @@ public enum Feature implements FormatFeature
* @since 2.17
*/
AUTO_DETECT_XSI_TYPE(false),

/**
* Feature that determines how floating-point infinity values are
* serialized.
*<p>
* By default, {@link Float#POSITIVE_INFINITY} and
* {@link Double#POSITIVE_INFINITY} are serialized as {@code Infinity},
* and {@link Float#NEGATIVE_INFINITY} and
* {@link Double#NEGATIVE_INFINITY} are serialized as
* {@code -Infinity}. This is the representation that Java normally
* uses for these values (see {@link Float#toString(float)} and
* {@link Double#toString(double)}), but JAXB and other XML
* Schema-conforming readers won't understand it.
*<p>
* With this feature enabled, these values are instead serialized as
* {@code INF} and {@code -INF}, respectively. This is the
* representation that XML Schema and JAXB use (see the XML Schema
* primitive types
* <a href="https://www.w3.org/TR/xmlschema-2/#float"><code>float</code></a>
* and
* <a href="https://www.w3.org/TR/xmlschema-2/#double"><code>double</code></a>).
*<p>
* When deserializing, Jackson always understands both representations,
* so there is no corresponding {@link FromXmlParser.Feature}.
*<p>
* Feature is disabled by default for backwards compatibility.
*/
XML_SCHEMA_CONFORMING_FLOATS(false),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty good but maybe add WRITE_ prefix to go along with existing WRITE_NULLS_AS_XSI_NIL?

;

final boolean _defaultState;
Expand Down Expand Up @@ -1174,6 +1203,11 @@ public void writeNumber(long l) throws IOException
@Override
public void writeNumber(double d) throws IOException
{
if (Double.isInfinite(d) && isEnabled(Feature.XML_SCHEMA_CONFORMING_FLOATS)) {
writeNumber(d > 0d ? "INF" : "-INF");
return;
}

_verifyValueWrite("write number");
if (_nextName == null) {
handleMissingName();
Expand Down Expand Up @@ -1202,6 +1236,11 @@ public void writeNumber(double d) throws IOException
@Override
public void writeNumber(float f) throws IOException
{
if (Float.isInfinite(f) && isEnabled(Feature.XML_SCHEMA_CONFORMING_FLOATS)) {
writeNumber(f > 0f ? "INF" : "-INF");
return;
}

_verifyValueWrite("write number");
if (_nextName == null) {
handleMissingName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,22 @@ static class AttrAndElem
public int attr = 42;
}

static class Floats
{
public float elem;

@JacksonXmlProperty(isAttribute=true, localName="attr")
public float attr;
}

static class Doubles
{
public double elem;

@JacksonXmlProperty(isAttribute=true, localName="attr")
public double attr;
}

static class WrapperBean<T>
{
public T value;
Expand Down Expand Up @@ -175,4 +191,62 @@ public void testJAXB() throws Exception
System.out.println("JAXB -> "+sw);
}
*/

public void testFloatInfinity() throws IOException
{
Floats infinite = new Floats();
infinite.attr = Float.POSITIVE_INFINITY;
infinite.elem = Float.NEGATIVE_INFINITY;

Floats finite = new Floats();
finite.attr = 42.5f;
finite.elem = 1337.875f;

checkFloatInfinity(infinite, false, "<Floats attr=\"Infinity\"><elem>-Infinity</elem></Floats>");
checkFloatInfinity(finite, false, "<Floats attr=\"42.5\"><elem>1337.875</elem></Floats>");
checkFloatInfinity(infinite, true, "<Floats attr=\"INF\"><elem>-INF</elem></Floats>");
checkFloatInfinity(finite, true, "<Floats attr=\"42.5\"><elem>1337.875</elem></Floats>");
}

private void checkFloatInfinity(Floats original, boolean xmlSchemaConforming, String expectedXml) throws IOException
{
_xmlMapper.configure(ToXmlGenerator.Feature.XML_SCHEMA_CONFORMING_FLOATS, xmlSchemaConforming);

String xml = _xmlMapper.writeValueAsString(original);
xml = removeSjsxpNamespace(xml);
assertEquals(expectedXml, xml);

Floats deserialized = _xmlMapper.readValue(xml, Floats.class);
assertEquals(original.attr, deserialized.attr);
assertEquals(original.elem, deserialized.elem);
}

public void testDoubleInfinity() throws IOException
{
Doubles infinite = new Doubles();
infinite.attr = Double.POSITIVE_INFINITY;
infinite.elem = Double.NEGATIVE_INFINITY;

Doubles finite = new Doubles();
finite.attr = 42.5d;
finite.elem = 1337.875d;

checkDoubleInfinity(infinite, false, "<Doubles attr=\"Infinity\"><elem>-Infinity</elem></Doubles>");
checkDoubleInfinity(finite, false, "<Doubles attr=\"42.5\"><elem>1337.875</elem></Doubles>");
checkDoubleInfinity(infinite, true, "<Doubles attr=\"INF\"><elem>-INF</elem></Doubles>");
checkDoubleInfinity(finite, true, "<Doubles attr=\"42.5\"><elem>1337.875</elem></Doubles>");
}

private void checkDoubleInfinity(Doubles original, boolean xmlSchemaConforming, String expectedXml) throws IOException
{
_xmlMapper.configure(ToXmlGenerator.Feature.XML_SCHEMA_CONFORMING_FLOATS, xmlSchemaConforming);

String xml = _xmlMapper.writeValueAsString(original);
xml = removeSjsxpNamespace(xml);
assertEquals(expectedXml, xml);

Doubles deserialized = _xmlMapper.readValue(xml, Doubles.class);
assertEquals(original.attr, deserialized.attr);
assertEquals(original.elem, deserialized.elem);
}
}