Skip to content

Commit dfdd167

Browse files
committed
Add a ToXmlGenerator.Feature to use XML Schema-compatible representation for floating-point infinity.
Fixes FasterXML#643.
1 parent b782f4b commit dfdd167

File tree

2 files changed

+113
-0
lines changed

2 files changed

+113
-0
lines changed

src/main/java/com/fasterxml/jackson/dataformat/xml/ser/ToXmlGenerator.java

+39
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.fasterxml.jackson.core.json.JsonWriteContext;
2121
import com.fasterxml.jackson.core.util.JacksonFeatureSet;
2222
import com.fasterxml.jackson.dataformat.xml.XmlPrettyPrinter;
23+
import com.fasterxml.jackson.dataformat.xml.deser.FromXmlParser;
2324
import com.fasterxml.jackson.dataformat.xml.util.DefaultXmlPrettyPrinter;
2425
import com.fasterxml.jackson.dataformat.xml.util.StaxUtil;
2526

@@ -106,6 +107,34 @@ public enum Feature implements FormatFeature
106107
* @since 2.17
107108
*/
108109
AUTO_DETECT_XSI_TYPE(false),
110+
111+
/**
112+
* Feature that determines how floating-point infinity values are
113+
* serialized.
114+
*<p>
115+
* By default, {@link Float#POSITIVE_INFINITY} and
116+
* {@link Double#POSITIVE_INFINITY} are serialized as {@code Infinity},
117+
* and {@link Float#NEGATIVE_INFINITY} and
118+
* {@link Double#NEGATIVE_INFINITY} are serialized as
119+
* {@code -Infinity}. This is the representation that Java normally
120+
* uses for these values (see {@link Float#toString(float)} and
121+
* {@link Double#toString(double)}), but JAXB and other XML
122+
* Schema-conforming readers won't understand it.
123+
*<p>
124+
* With this feature enabled, these values are instead serialized as
125+
* {@code INF} and {@code -INF}, respectively. This is the
126+
* representation that XML Schema and JAXB use (see the XML Schema
127+
* primitive types
128+
* <a href="https://www.w3.org/TR/xmlschema-2/#float"><code>float</code></a>
129+
* and
130+
* <a href="https://www.w3.org/TR/xmlschema-2/#double"><code>double</code></a>).
131+
*<p>
132+
* When deserializing, Jackson always understands both representations,
133+
* so there is no corresponding {@link FromXmlParser.Feature}.
134+
*<p>
135+
* Feature is disabled by default for backwards compatibility.
136+
*/
137+
XML_SCHEMA_CONFORMING_FLOATS(false),
109138
;
110139

111140
final boolean _defaultState;
@@ -1174,6 +1203,11 @@ public void writeNumber(long l) throws IOException
11741203
@Override
11751204
public void writeNumber(double d) throws IOException
11761205
{
1206+
if (Double.isInfinite(d) && isEnabled(Feature.XML_SCHEMA_CONFORMING_FLOATS)) {
1207+
writeNumber(d > 0d ? "INF" : "-INF");
1208+
return;
1209+
}
1210+
11771211
_verifyValueWrite("write number");
11781212
if (_nextName == null) {
11791213
handleMissingName();
@@ -1202,6 +1236,11 @@ public void writeNumber(double d) throws IOException
12021236
@Override
12031237
public void writeNumber(float f) throws IOException
12041238
{
1239+
if (Float.isInfinite(f) && isEnabled(Feature.XML_SCHEMA_CONFORMING_FLOATS)) {
1240+
writeNumber(f > 0f ? "INF" : "-INF");
1241+
return;
1242+
}
1243+
12051244
_verifyValueWrite("write number");
12061245
if (_nextName == null) {
12071246
handleMissingName();

src/test/java/com/fasterxml/jackson/dataformat/xml/ser/TestSerialization.java

+74
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,22 @@ static class AttrAndElem
3131
public int attr = 42;
3232
}
3333

34+
static class Floats
35+
{
36+
public float elem;
37+
38+
@JacksonXmlProperty(isAttribute=true, localName="attr")
39+
public float attr;
40+
}
41+
42+
static class Doubles
43+
{
44+
public double elem;
45+
46+
@JacksonXmlProperty(isAttribute=true, localName="attr")
47+
public double attr;
48+
}
49+
3450
static class WrapperBean<T>
3551
{
3652
public T value;
@@ -175,4 +191,62 @@ public void testJAXB() throws Exception
175191
System.out.println("JAXB -> "+sw);
176192
}
177193
*/
194+
195+
public void testFloatInfinity() throws IOException
196+
{
197+
Floats infinite = new Floats();
198+
infinite.attr = Float.POSITIVE_INFINITY;
199+
infinite.elem = Float.NEGATIVE_INFINITY;
200+
201+
Floats finite = new Floats();
202+
finite.attr = 42.5f;
203+
finite.elem = 1337.875f;
204+
205+
checkFloatInfinity(infinite, false, "<Floats attr=\"Infinity\"><elem>-Infinity</elem></Floats>");
206+
checkFloatInfinity(finite, false, "<Floats attr=\"42.5\"><elem>1337.875</elem></Floats>");
207+
checkFloatInfinity(infinite, true, "<Floats attr=\"INF\"><elem>-INF</elem></Floats>");
208+
checkFloatInfinity(finite, true, "<Floats attr=\"42.5\"><elem>1337.875</elem></Floats>");
209+
}
210+
211+
private void checkFloatInfinity(Floats original, boolean xmlSchemaConforming, String expectedXml) throws IOException
212+
{
213+
_xmlMapper.configure(ToXmlGenerator.Feature.XML_SCHEMA_CONFORMING_FLOATS, xmlSchemaConforming);
214+
215+
String xml = _xmlMapper.writeValueAsString(original);
216+
xml = removeSjsxpNamespace(xml);
217+
assertEquals(expectedXml, xml);
218+
219+
Floats deserialized = _xmlMapper.readValue(xml, Floats.class);
220+
assertEquals(original.attr, deserialized.attr);
221+
assertEquals(original.elem, deserialized.elem);
222+
}
223+
224+
public void testDoubleInfinity() throws IOException
225+
{
226+
Doubles infinite = new Doubles();
227+
infinite.attr = Double.POSITIVE_INFINITY;
228+
infinite.elem = Double.NEGATIVE_INFINITY;
229+
230+
Doubles finite = new Doubles();
231+
finite.attr = 42.5d;
232+
finite.elem = 1337.875d;
233+
234+
checkDoubleInfinity(infinite, false, "<Doubles attr=\"Infinity\"><elem>-Infinity</elem></Doubles>");
235+
checkDoubleInfinity(finite, false, "<Doubles attr=\"42.5\"><elem>1337.875</elem></Doubles>");
236+
checkDoubleInfinity(infinite, true, "<Doubles attr=\"INF\"><elem>-INF</elem></Doubles>");
237+
checkDoubleInfinity(finite, true, "<Doubles attr=\"42.5\"><elem>1337.875</elem></Doubles>");
238+
}
239+
240+
private void checkDoubleInfinity(Doubles original, boolean xmlSchemaConforming, String expectedXml) throws IOException
241+
{
242+
_xmlMapper.configure(ToXmlGenerator.Feature.XML_SCHEMA_CONFORMING_FLOATS, xmlSchemaConforming);
243+
244+
String xml = _xmlMapper.writeValueAsString(original);
245+
xml = removeSjsxpNamespace(xml);
246+
assertEquals(expectedXml, xml);
247+
248+
Doubles deserialized = _xmlMapper.readValue(xml, Doubles.class);
249+
assertEquals(original.attr, deserialized.attr);
250+
assertEquals(original.elem, deserialized.elem);
251+
}
178252
}

0 commit comments

Comments
 (0)