我有一个从 xsd 生成的对象:
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "myClass", propOrder = {
"id",
"code"
})
public class MyClass {
@XmlElement(required = true)
protected String id;
@XmlElement(required = true)
protected String code;
...
}
我得到一个字符串:
<id>1234dddd</id><code>ddsw</code>
我想将此字符串解压缩为
MyClass
的对象。
try (StringReader reader = new StringReader(xml)) {
JAXBContext jaxbContext = JAXBContext.newInstance(MyClass.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
JAXBElement<MyClass> jaxbElement = (JAXBElement<MyClass>) unmarshaller.unmarshal(reader);
return jaxbElement.getValue();
} catch (Exception e) {
throw new RuntimeException("Ошибка десериализации", e);
}
但是我得到了一个错误:
unexpected element (uri:"", local:"id"). Expected elements are (none)
我无法更改 MyClass 因为它是生成的,我如何从字符串中获取它的对象?
此处显示的解决方案执行以下两件事:
Unmarshaller
方法来声明 MyClass
为目标对象。MyClassStringReaer
将 XML 片段封装在 <myClass>...</mClass>
根元素中。输出为:
myClass: org.example.myclass.MyClass@6b9651f3
id...: 1234dddd
code.: ddsw
当 JAXB 类未声明自身为
unmarshal
时,可以使用此 @XmlRootElement
方法。它显式地声明了目标类。
<T> JAXBElement<T> unmarshal(javax.xml.transform.Source source, Class<T> declaredType)
虽然该方法需要 XML
Source
参数,但这可以轻松地从 I/O 读取器或流对象创建,如演示中所示。
此演示使用名为
MyClassStringReader
的自定义阅读器类,请参阅下文。可以使用 <myClass>...</myClass>
连接将要解组的 XML 片段包装在 String
根元素中,而不是使用此方法;但是,自定义读取器是 I/O 装饰的一个很好的演示,它可以用作文件或其他基于流的读取器的模板,在这些读取器中串联是一个糟糕的选择。
演示.java
package org.example.myclass;
import java.io.StringReader;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBElement;
import jakarta.xml.bind.Unmarshaller;
public class Demo
{
@SuppressWarnings("unchecked")
public static void main(String[] args)
{
String txt = "<id>1234dddd</id><code>ddsw</code>";
try
{
JAXBContext jaxbContext = JAXBContext.newInstance(MyClass.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
try (StringReader reader = new MyClassStringReader(txt))
{
// reader: "<myClass><id>1234dddd</id><code>ddsw</code></myClass>";
Source source = new StreamSource(reader);
Object obj = unmarshaller.unmarshal(source, MyClass.class);
if ( obj instanceof JAXBElement )
{
MyClass myClass = ((JAXBElement<MyClass>) obj).getValue();
println("myClass: " + myClass);
println(" id...: " + myClass.id);
println(" code.: " + myClass.code);
}
else
println("Object.:" + obj);
}
}
catch (Exception ex)
{
throw new RuntimeException("Ошибка десериализации", ex);
}
}
private static void println(String str)
{
System.out.println(str);
}
}
这个
MyClassStringReader
类扩展了 java.io.StringReader
,以用所需的根元素封装仅包含 myClass
元素的子元素的任何字符串。 XML 始终需要一个根元素。它通过将大部分工作委托给其 Reader
超级实例来覆盖抽象 StringReader
使用的所有公共方法。这是一个快速实现,需要进行彻底的测试才能更广泛地使用。MyClassStringReader.java
package org.example.myclass;
import java.io.IOException;
import java.io.StringReader;
/**
* Envelop a {@link StringReader} with a root element.
*/
public class MyClassStringReader extends StringReader
{
private static final String ROOT_OPEN = "<myClass>";
private static final String ROOT_CLOSE = "</myClass>";
private final StringReader ROOT_OPEN_READER = new StringReader(ROOT_OPEN);
private final StringReader ROOT_CLOSE_READER = new StringReader(ROOT_CLOSE);
/**
* Construct with string.
* @param str The string to stream.
*/
public MyClassStringReader(String str)
{
super(str);
}
@Override
public int read() throws IOException
{
int nextChar = ROOT_OPEN_READER.read();
if ( nextChar < 0 )
{
nextChar = super.read();
if ( nextChar < 0 )
nextChar = ROOT_CLOSE_READER.read();
}
return nextChar;
}
@Override
public int read(char[] cbuf, int off, int len) throws IOException
{
// Guard len
if ( len > cbuf.length)
len = cbuf.length;
// Skip offset characters
int nextChar = 0;
while ( (--off > 0) && ((nextChar = read()) >= 0) );
// Read len or less characters
int counter = 0;
if ( nextChar >= 0 )
{
while ( (counter < len) && ((nextChar = read()) >= 0) )
cbuf[counter++] = (char) nextChar;
}
// Return number of characters read or EOS
return (counter > 0 ) ? counter : -1;
}
@Override
public boolean ready() throws IOException
{
return ROOT_OPEN_READER.ready() || super.ready() || ROOT_CLOSE_READER.ready();
}
@Override
public void reset() throws IOException
{
ROOT_OPEN_READER.reset();
super.reset();
ROOT_CLOSE_READER.reset();
}
@Override
public long skip(long n) throws IOException
{
long s = n;
if ( n > 0L )
{
if ( ROOT_OPEN_READER.ready() )
s -= ROOT_OPEN_READER.skip(s);
if ( (s > 0) && super.ready() )
s -= super.skip(s);
if ( (s > 0) && ROOT_CLOSE_READER.ready() )
s -= ROOT_CLOSE_READER.skip(s);
}
else if ( n < 0L )
throw new IllegalArgumentException("skip value is negative");
return (n-s);
}
@Override
public void mark(int readAheadLimit) throws IOException
{
if ( ROOT_OPEN_READER.ready() )
ROOT_OPEN_READER.mark(readAheadLimit);
else if (super.ready() )
super.mark(readAheadLimit);
else if ( ROOT_CLOSE_READER.ready() )
ROOT_CLOSE_READER.mark(readAheadLimit);
}
}