Jackson XML :: 在没有打开的开始元素时尝试编写属性

Posted

技术标签:

【中文标题】Jackson XML :: 在没有打开的开始元素时尝试编写属性【英文标题】:Jackson XML :: Trying to write an attribute when there is no open start element 【发布时间】:2021-12-11 12:19:43 【问题描述】:

我有一个 Java 类,我想对 XML 进行序列化/反序列化。此类的每个属性都应与 XML 元素的属性进行序列化/反序列化。

XML 看起来像:

<element fullName="Asim" age="30" score="0.78" readonly="true" bounds="[0,0][10,20]" tags="tag1,tag2,tag3">
  ...
  ...
</element>

如果属性很简单(Stringintboolean),则可以。我可以简单地使用@JacksonXmlProperty 注释就可以完成工作:

@JacksonXmlProperty(localName = "fullName", isAttribute = true)
private String fullName;

但是,某些属性是类对象(boundslist),我需要在序列化/反序列化期间进行转换。我已经能够使用@JsonDeserialize注解来读取XML了:

@JacksonXmlProperty(localName = "bounds", isAttribute = true)
@JsonDeserialize(using = BoundsDeserializer.class)
private Rectangle bounds;

另一方面,序列化这些字段被证明是一个相当大的挑战。我试过使用JsonSerialize(using = BoundsSerializer.class)JsonSerialize(converter = BoundsConverter.class),但没有任何效果。要么我得到以下异常:

com.fasterxml.jackson.core.JsonGenerationException: Trying to write an attribute when there is no open start element.

或者生成的 XML 看起来像:

<element fullName="Asim" age="30" score="0.78" readonly="true">
  <bounds>
    <x>0</x>
    <y>0</y>
    <width>10</width>
    <height>20</width>
  </bounds>
  <tags tags="tag1" tags="tag2" tags="tag3" />
  ...
  ...
</element>

如何将类的非原始属性序列化为 XML 中的字符串属性?


编辑

主要调用代码:

try 
    XmlMapper mapper = new XmlMapper();
    mapper.enable(SerializationFeature.INDENT_OUTPUT);
    String xml = mapper.writeValueAsString(this);
    return xml;

catch (Exception ex)  ... 

要序列化/反序列化的类的相关位:注释行是我尝试(但失败)的替代方法。

@JacksonXmlRootElement(localName = "root")
@JsonIgnoreProperties(ignoreUnknown = true)
@Getter
@EqualsAndHashCode
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder(toBuilder = true)
@Slf4j
public class XML 

    @JacksonXmlProperty(localName = "text", isAttribute = true)
    private String text;

    @JacksonXmlProperty(localName = "tags", isAttribute = true)
    @JsonDeserialize(using = ListDeserializer.class)
    @JsonSerialize(converter = ListConverter.class)
    //@JsonSerialize(using = ListSerializer.class)
    //@JsonUnwrapped
    private List<String> tags;

    @JacksonXmlProperty(localName = "bounds", isAttribute = true)
    @JsonDeserialize(using = BoundsDeserializer.class)
    //@JsonSerialize(using = BoundsSerializer.class, contentAs = String.class)
    private Rectangle bounds;


边界反序列化器:

public class BoundsDeserializer extends JsonDeserializer<Rectangle> 

    private static final Pattern BOUNDS_PATTERN = Pattern.compile("\\[(-?\\d+),(-?\\d+)]\\[(-?\\d+),(-?\\d+)]");

    @Override
    @Nullable
    public Rectangle deserialize(JsonParser p, DeserializationContext ctxt) throws IOException 
        String value = p.getValueAsString();
        if (value.isBlank()) 
            return null;
        

        Matcher m = BOUNDS_PATTERN.matcher(value);
        if (!m.matches()) 
            return ctxt.reportInputMismatch(Rectangle.class, "Not a valid bounds string: '%s'", value);
        

        final int x1 = Integer.parseInt(m.group(1));
        final int y1 = Integer.parseInt(m.group(2));
        final int x2 = Integer.parseInt(m.group(3));
        final int y2 = Integer.parseInt(m.group(4));

        return new Rectangle(x1, y1, x2 - x1, y2 - y1);
    


列表反序列化器:

public class ListDeserializer extends JsonDeserializer<List<String>> 

    @Override
    @Nullable
    public List<String> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException 
        String value = p.getValueAsString();
        if (value.isBlank()) 
            return null;
        

        String deBracketed = value.trim().replaceAll("^\\[(.*)]$", "$1");
        List<String> listValues = Arrays.stream(deBracketed.split(","))
                .map(String::trim)
                .filter(Predicate.not(String::isEmpty))
                .collect(Collectors.toUnmodifiableList());
        return listValues;
    


列表转换器:

public class ListConverter extends StdConverter<List<String>, String> 

    @Override
    public String convert(List<String> list) 
        return String.join(",", list);
    


谢谢! 阿西姆

【问题讨论】:

【参考方案1】:

所以我解决这个问题的方法如下:

@JacksonXmlProperty(localName = "tags", isAttribute = true)
@JsonDeserialize(using = ListDeserializer.class)
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private List<String> tags;

@JacksonXmlProperty(localName = "tags", isAttribute = true)
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private String tagsAsString() 
    return String.join(",", this.tags);

第一个字段是只写的,所以它只会被反序列化。第二个字段是只读的,所以它只会被序列化。然而,这感觉真的很hackish,我认为必须有比这更好的解决方案。 :/

【讨论】:

【参考方案2】:

我想出了一个更好的方法来做同样的事情:

// Used during serialization
@JacksonXmlProperty(localName = "bounds", isAttribute = true)
@JsonSerialize(converter = RectangleToStringConverter.class)

// Used during deserialization
@JsonProperty("bounds")
@JsonDeserialize(converter = StringToRectangleConverter.class)

private Rectangle bounds;

【讨论】:

以上是关于Jackson XML :: 在没有打开的开始元素时尝试编写属性的主要内容,如果未能解决你的问题,请参考以下文章

Jackson XML Mapper为列表编写两次元素名称

Jackson 序列化:将字段值设置为 XML 元素名称

Jackson XML ArrayList 输出具有两个包装器元素

解析xml的工具类 -----jackson

Jackson xml 和 json 根元素

无法使用 Jackson XML 直接在根元素内反序列化列表