diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index 7f727d3e7..9f6dab8a0 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -4,6 +4,11 @@ Project: jackson-dataformat-xml = Releases ------------------------------------------------------------------------ +2.10.2 (not yet released) + +#378: Jackson 2.10.x fails to deserialize xsi:nil with multiple child elements + (reported by henrik242@github) + 2.10.1 (09-Nov-2019) - Upgrade Woodstox dependency to 6.0.2 diff --git a/src/main/java/com/fasterxml/jackson/dataformat/xml/deser/FromXmlParser.java b/src/main/java/com/fasterxml/jackson/dataformat/xml/deser/FromXmlParser.java index 423f14e01..6725376cd 100644 --- a/src/main/java/com/fasterxml/jackson/dataformat/xml/deser/FromXmlParser.java +++ b/src/main/java/com/fasterxml/jackson/dataformat/xml/deser/FromXmlParser.java @@ -185,14 +185,14 @@ public FromXmlParser(IOContext ctxt, int genericParserFeatures, int xmlFeatures, _parsingContext = XmlReadContext.createRootContext(-1, -1); _xmlTokens = new XmlTokenStream(xmlReader, ctxt.getSourceReference(), _formatFeatures); - switch (_xmlTokens.getCurrentToken()) { - case XmlTokenStream.XML_START_ELEMENT: - _nextToken = JsonToken.START_OBJECT; - break; - case XmlTokenStream.XML_NULL: + + // 04-Jan-2019, tatu: Root-level nulls need slightly specific handling; + // changed in 2.10.2 + if (_xmlTokens.hasXsiNil()) { _nextToken = JsonToken.VALUE_NULL; - break; - default: + } else if (_xmlTokens.getCurrentToken() == XmlTokenStream.XML_START_ELEMENT) { + _nextToken = JsonToken.START_OBJECT; + } else { _reportError("Internal problem: invalid starting state (%d)", _xmlTokens.getCurrentToken()); } } @@ -606,29 +606,28 @@ public JsonToken nextToken() throws IOException } } return (_currToken = JsonToken.VALUE_STRING); - } else { - // [dataformat-xml#177]: empty text may also need to be skipped - // but... [dataformat-xml#191]: looks like we can't short-cut, must - // loop over again - if (_parsingContext.inObject()) { - if ((_currToken != JsonToken.FIELD_NAME) && _isEmpty(_currText)) { - try { - token = _xmlTokens.next(); - } catch (XMLStreamException e) { - StaxUtil.throwAsParseException(e, this); - } - continue; + } + // [dataformat-xml#177]: empty text may also need to be skipped + // but... [dataformat-xml#191]: looks like we can't short-cut, must + // loop over again + if (_parsingContext.inObject()) { + if ((_currToken != JsonToken.FIELD_NAME) && _isEmpty(_currText)) { + try { + token = _xmlTokens.next(); + } catch (XMLStreamException e) { + StaxUtil.throwAsParseException(e, this); } + continue; } } // If not a leaf (or otherwise ignorable), need to transform into property... _parsingContext.setCurrentName(_cfgNameForTextElement); _nextToken = JsonToken.VALUE_STRING; return (_currToken = JsonToken.FIELD_NAME); - case XmlTokenStream.XML_NULL: - return (_currToken = JsonToken.VALUE_NULL); case XmlTokenStream.XML_END: return (_currToken = null); + default: + return _internalErrorUnknownToken(token); } } } @@ -754,11 +753,10 @@ public String nextTextValue() throws IOException _nextToken = JsonToken.VALUE_STRING; _currToken = JsonToken.FIELD_NAME; break; - case XmlTokenStream.XML_NULL: - _currToken = JsonToken.VALUE_STRING; - return (_currText = null); case XmlTokenStream.XML_END: _currToken = null; + default: + return _internalErrorUnknownToken(token); } return null; } @@ -782,6 +780,7 @@ private void _updateState(JsonToken t) _parsingContext.setCurrentName(_xmlTokens.getLocalName()); break; default: + _internalErrorUnknownToken(t); } } @@ -1052,4 +1051,8 @@ protected boolean _isEmpty(String str) } return true; } + + private T _internalErrorUnknownToken(Object token) { + throw new IllegalStateException("Internal error: unrecognized XmlTokenStream token: "+token); + } } diff --git a/src/main/java/com/fasterxml/jackson/dataformat/xml/deser/XmlTokenStream.java b/src/main/java/com/fasterxml/jackson/dataformat/xml/deser/XmlTokenStream.java index e31926a8e..2bf3b5374 100644 --- a/src/main/java/com/fasterxml/jackson/dataformat/xml/deser/XmlTokenStream.java +++ b/src/main/java/com/fasterxml/jackson/dataformat/xml/deser/XmlTokenStream.java @@ -31,8 +31,8 @@ public class XmlTokenStream public final static int XML_ATTRIBUTE_NAME = 3; public final static int XML_ATTRIBUTE_VALUE = 4; public final static int XML_TEXT = 5; - public final static int XML_NULL = 6; // since 2.10 - public final static int XML_END = 7; + + public final static int XML_END = 6; // // // token replay states @@ -143,12 +143,7 @@ public XmlTokenStream(XMLStreamReader xmlReader, Object sourceRef, _formatFeatures = formatFeatures; _checkXsiAttributes(); // sets _attributeCount, _nextAttributeIndex - - if (_xsiNilFound) { - _currentState = XML_NULL; - } else { - _currentState = XML_START_ELEMENT; - } + _currentState = XML_START_ELEMENT; } public XMLStreamReader2 getXmlReader() { @@ -189,9 +184,6 @@ public int next() throws XMLStreamException case XML_TEXT: System.out.println(" XML-token: XML_TEXT '"+_textValue+"'"); break; - case XML_NULL: - System.out.println(" XML-token: XML_NULL"); - break; case XML_END: System.out.println(" XML-token: XML_END"); break; @@ -224,6 +216,10 @@ public void skipEndElement() throws IOException, XMLStreamException public String getLocalName() { return _localName; } public String getNamespaceURI() { return _namespaceURI; } + public boolean hasXsiNil() { + return _xsiNilFound; + } + /*// not used as of 2.10 public boolean hasAttributes() { return (_currentState == XML_START_ELEMENT) && (_attributeCount > 0); @@ -349,9 +345,15 @@ private final int _next() throws XMLStreamException // 06-Sep-2019, tatu: `xsi:nil` to induce "real" null value? if (_xsiNilFound) { _xsiNilFound = false; - return (_currentState = XML_NULL); + switch (_skipUntilTag()) { + case XMLStreamConstants.END_ELEMENT: + return _handleEndElement(); + case XMLStreamConstants.END_DOCUMENT: + throw new IllegalStateException("Unexpected end-of-input after null token"); + default: + } + throw new IllegalStateException("Unexpected START_ELEMENT after null token"); } - if (_nextAttributeIndex < _attributeCount) { _localName = _xmlReader.getAttributeLocalName(_nextAttributeIndex); _namespaceURI = _xmlReader.getAttributeNamespace(_nextAttributeIndex); @@ -385,24 +387,12 @@ private final int _next() throws XMLStreamException return (_currentState = XML_ATTRIBUTE_VALUE); case XML_TEXT: // mixed text with other elements - if (_mixedText){ + if (_mixedText) { _mixedText = false; return _initStartElement(); } // text followed by END_ELEMENT return _handleEndElement(); - case XML_NULL: - // at this point we are pointing to START_ELEMENT, need to find - // matching END_ELEMENT, handle it - // 06-Sep-2019, tatu: Should handle error cases better but for now this'll do - switch (_skipUntilTag()) { - case XMLStreamConstants.END_ELEMENT: - return _handleEndElement(); - case XMLStreamConstants.END_DOCUMENT: - throw new IllegalStateException("Unexpected end-of-input after null token"); - default: - throw new IllegalStateException("Unexpected START_ELEMENT after null token"); - } case XML_END: return XML_END; diff --git a/src/test/java/com/fasterxml/jackson/dataformat/xml/failing/XsiNil378Test.java b/src/test/java/com/fasterxml/jackson/dataformat/xml/deser/XsiNil378Test.java similarity index 88% rename from src/test/java/com/fasterxml/jackson/dataformat/xml/failing/XsiNil378Test.java rename to src/test/java/com/fasterxml/jackson/dataformat/xml/deser/XsiNil378Test.java index 167f9b2fb..6aade80df 100644 --- a/src/test/java/com/fasterxml/jackson/dataformat/xml/failing/XsiNil378Test.java +++ b/src/test/java/com/fasterxml/jackson/dataformat/xml/deser/XsiNil378Test.java @@ -1,4 +1,4 @@ -package com.fasterxml.jackson.dataformat.xml.failing; +package com.fasterxml.jackson.dataformat.xml.deser; import com.fasterxml.jackson.dataformat.xml.XmlMapper; import com.fasterxml.jackson.dataformat.xml.XmlTestBase; @@ -33,6 +33,7 @@ public void testWithStringAsNull2() throws Exception bean = MAPPER.readValue( "not null", +//"not null", StringPair.class); assertNotNull(bean); assertNull(bean.first);