diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index 9a72ddfda..f1bb218f7 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -6,7 +6,8 @@ Project: jackson-dataformat-xml 2.11.1 (not yet released) -- +#393: `MismatchedInputException` for nested repeating element name in `List` + (reported by kaizenHorse@github) 2.11.0 (26-Apr-2020) 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 fecfe36ef..10846e771 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 @@ -85,7 +85,7 @@ private Feature(boolean defaultState) { *

* Name used for pseudo-property used for returning XML Text value (which does * not have actual element name to use). Defaults to empty String, but - * may be changed for interoperability reasons: JAXB, for example, uses + * may be changed for inter-operability reasons: JAXB, for example, uses * "value" as name. * * @since 2.1 @@ -146,8 +146,6 @@ private Feature(boolean defaultState) { protected String _currText; - protected Set _namesToWrap; - /* /********************************************************** /* Parsing state, parsed values @@ -323,14 +321,14 @@ public XMLStreamReader getStaxReader() { */ public void addVirtualWrapping(Set namesToWrap) { - /* 17-Sep-2012, tatu: Not 100% sure why, but this is necessary to avoid - * problems with Lists-in-Lists properties - */ +//System.out.println("addVirtualWrapping("+namesToWrap+")"); + // 17-Sep-2012, tatu: Not 100% sure why, but this is necessary to avoid + // problems with Lists-in-Lists properties String name = _xmlTokens.getLocalName(); if (name != null && namesToWrap.contains(name)) { +//System.out.println("REPEAT from addVirtualWrapping()"); _xmlTokens.repeatStartElement(); } - _namesToWrap = namesToWrap; _parsingContext.setNamesToWrap(namesToWrap); } @@ -435,7 +433,7 @@ public boolean isExpectedStartArrayToken() _currToken = JsonToken.START_ARRAY; // Ok: must replace current context with array as well _parsingContext.convertToArray(); -//System.out.println(" isExpectedArrayStart: OBJ->Array, wraps now: "+_parsingContext.getNamesToWrap()); +//System.out.println(" FromXmlParser.isExpectedArrayStart(): OBJ->Array"); // And just in case a field name was to be returned, wipe it // 06-Jan-2015, tatu: Actually, could also be empty Object buffered; if so, convert... if (_nextToken == JsonToken.END_OBJECT) { @@ -460,13 +458,13 @@ public JsonToken nextToken() throws IOException if (t != null) { switch (t) { case FIELD_NAME: - System.out.println("JsonToken: FIELD_NAME '"+_parsingContext.getCurrentName()+"'"); + System.out.println("FromXmlParser.nextToken(): JsonToken.FIELD_NAME '"+_parsingContext.getCurrentName()+"'"); break; case VALUE_STRING: - System.out.println("JsonToken: VALUE_STRING '"+getText()+"'"); + System.out.println("FromXmlParser.nextToken(): JsonToken.VALUE_STRING '"+getText()+"'"); break; default: - System.out.println("JsonToken: "+t); + System.out.println("FromXmlParser.nextToken(): "+t); } } return t; @@ -492,7 +490,6 @@ public JsonToken nextToken() throws IOException case END_OBJECT: case END_ARRAY: _parsingContext = _parsingContext.getParent(); - _namesToWrap = _parsingContext.getNamesToWrap(); break; case FIELD_NAME: _parsingContext.setCurrentName(_xmlTokens.getLocalName()); @@ -534,7 +531,8 @@ public JsonToken nextToken() throws IOException // Ok: virtual wrapping can be done by simply repeating current START_ELEMENT. // Couple of ways to do it; but start by making _xmlTokens replay the thing... - if (_namesToWrap != null && _namesToWrap.contains(name)) { + if (_parsingContext.shouldWrap(name)) { +//System.out.println("REPEAT from nextToken()"); _xmlTokens.repeatStartElement(); } @@ -565,7 +563,6 @@ public JsonToken nextToken() throws IOException } _currToken = _parsingContext.inArray() ? JsonToken.END_ARRAY : JsonToken.END_OBJECT; _parsingContext = _parsingContext.getParent(); - _namesToWrap = _parsingContext.getNamesToWrap(); return _currToken; case XmlTokenStream.XML_ATTRIBUTE_NAME: @@ -696,7 +693,8 @@ public String nextTextValue() throws IOException } String name = _xmlTokens.getLocalName(); _parsingContext.setCurrentName(name); - if (_namesToWrap != null && _namesToWrap.contains(name)) { + if (_parsingContext.shouldWrap(name)) { +//System.out.println("REPEAT from nextTextValue()"); _xmlTokens.repeatStartElement(); } _mayBeLeaf = true; @@ -715,7 +713,6 @@ public String nextTextValue() throws IOException } _currToken = _parsingContext.inArray() ? JsonToken.END_ARRAY : JsonToken.END_OBJECT; _parsingContext = _parsingContext.getParent(); - _namesToWrap = _parsingContext.getNamesToWrap(); break; case XmlTokenStream.XML_ATTRIBUTE_NAME: // If there was a chance of leaf node, no more... @@ -774,7 +771,6 @@ private void _updateState(JsonToken t) case END_OBJECT: case END_ARRAY: _parsingContext = _parsingContext.getParent(); - _namesToWrap = _parsingContext.getNamesToWrap(); break; case FIELD_NAME: _parsingContext.setCurrentName(_xmlTokens.getLocalName()); @@ -835,7 +831,6 @@ public String getValueAsString(String defValue) throws IOException // note: Should NOT update context, because we will still be getting // matching END_OBJECT, which will undo contexts properly _parsingContext = _parsingContext.getParent(); - _namesToWrap = _parsingContext.getNamesToWrap(); _currToken = JsonToken.VALUE_STRING; _nextToken = null; // One more thing: must explicitly skip the END_OBJECT that would follow diff --git a/src/main/java/com/fasterxml/jackson/dataformat/xml/deser/XmlReadContext.java b/src/main/java/com/fasterxml/jackson/dataformat/xml/deser/XmlReadContext.java index 5a341e4ae..5562a59f5 100644 --- a/src/main/java/com/fasterxml/jackson/dataformat/xml/deser/XmlReadContext.java +++ b/src/main/java/com/fasterxml/jackson/dataformat/xml/deser/XmlReadContext.java @@ -176,10 +176,16 @@ public void setNamesToWrap(Set namesToWrap) { _namesToWrap = namesToWrap; } - public Set getNamesToWrap() { + @Deprecated // since 2.11.1 + public Set getNamesToWrap() { return _namesToWrap; } + // @since 2.11.1 + public boolean shouldWrap(String localName) { + return (_namesToWrap != null) && _namesToWrap.contains(localName); + } + protected void convertToArray() { _type = TYPE_ARRAY; } 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 2bf3b5374..7624975fe 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 @@ -170,22 +170,22 @@ public int next() throws XMLStreamException int n = next0(); switch (n) { case XML_START_ELEMENT: - System.out.println(" XML-token: XML_START_ELEMENT '"+_localName+"'"); + System.out.println(" XmlTolenStream.next(): XML_START_ELEMENT '"+_localName+"'"); break; case XML_END_ELEMENT: - System.out.println(" XML-token: XML_END_ELEMENT '"+_localName+"'"); + System.out.println(" XmlTolenStream.next(): XML_END_ELEMENT '"+_localName+"'"); break; case XML_ATTRIBUTE_NAME: - System.out.println(" XML-token: XML_ATTRIBUTE_NAME '"+_localName+"'"); + System.out.println(" XmlTolenStream.next(): XML_ATTRIBUTE_NAME '"+_localName+"'"); break; case XML_ATTRIBUTE_VALUE: - System.out.println(" XML-token: XML_ATTRIBUTE_VALUE '"+_textValue+"'"); + System.out.println(" XmlTolenStream.next(): XML_ATTRIBUTE_VALUE '"+_textValue+"'"); break; case XML_TEXT: - System.out.println(" XML-token: XML_TEXT '"+_textValue+"'"); + System.out.println(" XmlTolenStream.next(): XML_TEXT '"+_textValue+"'"); break; case XML_END: - System.out.println(" XML-token: XML_END"); + System.out.println(" XmlTolenStream.next(): XML_END"); break; default: throw new IllegalStateException(); @@ -194,7 +194,8 @@ public int next() throws XMLStreamException } */ - public int next() throws XMLStreamException +// public int next0() throws XMLStreamException + public int next() throws XMLStreamException { if (_repeatElement != 0) { return (_currentState = _handleRepeatElement()); @@ -256,7 +257,7 @@ public JsonLocation getTokenLocation() { */ protected void repeatStartElement() { -//System.out.println(" -> repeatStartElement for "+_localName); +//System.out.println(" -> repeatStartElement for "+_localName+", _currentWrapper was: "+_currentWrapper); // sanity check: can only be used when just returned START_ELEMENT: if (_currentState != XML_START_ELEMENT) { throw new IllegalStateException("Current state not XML_START_ELEMENT (" @@ -264,10 +265,11 @@ protected void repeatStartElement() } // Important: add wrapper, to keep track... if (_currentWrapper == null) { - _currentWrapper = ElementWrapper.matchingWrapper(_currentWrapper, _localName, _namespaceURI); + _currentWrapper = ElementWrapper.matchingWrapper(null, _localName, _namespaceURI); } else { _currentWrapper = ElementWrapper.matchingWrapper(_currentWrapper.getParent(), _localName, _namespaceURI); } +//System.out.println(" repeatStartElement for "+_localName+", _currentWrapper now: "+_currentWrapper); _repeatElement = REPLAY_START_DUP; } @@ -523,12 +525,13 @@ private final int _initStartElement() throws XMLStreamException if (_currentWrapper != null) { if (_currentWrapper.matchesWrapper(localName, ns)) { _currentWrapper = _currentWrapper.intermediateWrapper(); +//System.out.println(" _initStartElement(): START_ELEMENT ("+localName+") DOES match ["+_currentWrapper+"]: leave/add intermediate"); } else { // implicit end is more interesting: +//System.out.println(" _initStartElement(): START_ELEMENT ("+localName+") not matching '"+_localName+"'; add extra XML-END-ELEMENT!"); _localName = _currentWrapper.getWrapperLocalName(); _namespaceURI = _currentWrapper.getWrapperNamespace(); _currentWrapper = _currentWrapper.getParent(); -//System.out.println(" START_ELEMENT ("+localName+") not matching '"+_localName+"'; add extra XML-END-ELEMENT!"); // Important! We also need to restore the START_ELEMENT, so: _nextLocalName = localName; _nextNamespaceURI = ns; @@ -571,16 +574,18 @@ private final void _checkXsiAttributes() { */ protected int _handleRepeatElement() throws XMLStreamException { +//System.out.println(" XMLTokenStream._handleRepeatElement()"); + int type = _repeatElement; _repeatElement = 0; if (type == REPLAY_START_DUP) { -//System.out.println("handleRepeat for START_ELEMENT: "+_localName+" ("+_xmlReader.getLocalName()+")"); +//System.out.println(" XMLTokenStream._handleRepeatElement() for START_ELEMENT: "+_localName+" ("+_xmlReader.getLocalName()+")"); // important: add the virtual element second time, but not with name to match _currentWrapper = _currentWrapper.intermediateWrapper(); return XML_START_ELEMENT; } if (type == REPLAY_END) { -//System.out.println("handleRepeat for END_ELEMENT: "+_localName+" ("+_xmlReader.getLocalName()+")"); +//System.out.println(" XMLTokenStream._handleRepeatElement() for END_ELEMENT: "+_localName+" ("+_xmlReader.getLocalName()+")"); _localName = _xmlReader.getLocalName(); _namespaceURI = _xmlReader.getNamespaceURI(); if (_currentWrapper != null) { @@ -596,8 +601,8 @@ protected int _handleRepeatElement() throws XMLStreamException _namespaceURI = _nextNamespaceURI; _nextLocalName = null; _nextNamespaceURI = null; - -//System.out.println("handleRepeat for START_DELAYED: "+_localName+" ("+_xmlReader.getLocalName()+")"); + +//System.out.println(" XMLTokenStream._handleRepeatElement() for START_DELAYED: "+_localName+" ("+_xmlReader.getLocalName()+")"); return XML_START_ELEMENT; } @@ -606,6 +611,7 @@ protected int _handleRepeatElement() throws XMLStreamException private final int _handleEndElement() { +//System.out.println(" XMLTokenStream._handleEndElement()"); if (_currentWrapper != null) { ElementWrapper w = _currentWrapper; // important: if we close the scope, must duplicate END_ELEMENT as well @@ -614,7 +620,7 @@ private final int _handleEndElement() _localName = w.getWrapperLocalName(); _namespaceURI = w.getWrapperNamespace(); _currentWrapper = _currentWrapper.getParent(); -//System.out.println(" IMPLICIT requestRepeat of END_ELEMENT '"+_localName); +//System.out.println(" XMLTokenStream._handleEndElement(): IMPLICIT requestRepeat of END_ELEMENT '"+_localName); } else { _currentWrapper = _currentWrapper.getParent(); } diff --git a/src/test/java/com/fasterxml/jackson/dataformat/xml/failing/Issue393DeserTest.java b/src/test/java/com/fasterxml/jackson/dataformat/xml/failing/Issue393DeserTest.java index c179737e1..d4b7210ce 100644 --- a/src/test/java/com/fasterxml/jackson/dataformat/xml/failing/Issue393DeserTest.java +++ b/src/test/java/com/fasterxml/jackson/dataformat/xml/failing/Issue393DeserTest.java @@ -28,7 +28,6 @@ public Prices getPrices() { } } -// @JsonIgnoreProperties(ignoreUnknown = true) @JacksonXmlRootElement(localName = "prices") static class Prices { private List price = new ArrayList(); @@ -38,14 +37,11 @@ public void setPrice(List price) { } @JacksonXmlElementWrapper(useWrapping = false) - @JacksonXmlProperty(localName = "price") public List getPrice() { return this.price; } } -// @JacksonXmlRootElement(localName = "price") -// @JsonIgnoreProperties(ignoreUnknown = true) static class Price { private String price; private String num; @@ -60,7 +56,6 @@ public void setPrice(String price) { this.price = price; } - @JacksonXmlProperty(localName = "price") public String getPrice() { return this.price; } @@ -69,7 +64,6 @@ public void setNum(String num) { this.num = num; } - @JacksonXmlProperty(localName = "num") public String getNum() { return this.num; }