使用JAXB将XSD模式转换为JSON
libai 发布于 2022-09-11

考虑一个用例,其中给出了一个XML Schema.xsd文件,您应该基于该文件将XML转换为JSON格式。对此没有直接的解决方案。我们需要做以下工作:-

  • 首先,我们需要从XSD schema.XSD文件生成Java类文件
  • 使用Jackson进行反序列化(XML到Java对象)和序列化(Java对象到JSON),这将导致XML到JSON的转换。

我们希望尽可能地自动化这一点,以便在XML模式中有任何更新时,可以以最小的更改进行调整。我们将在这里创建一个JAXB任务(gradle或maven),它与项目构建阶段绑定,负责从给定的schema.xsd文件生成Java类文件。

当我们使用生成的Java类文件将XML转换为JSON文件时,我们发现主要有两个问题不符合我们的要求,并解决了它们:-

  • XSD xs:Date和xs:Time中的日期和时间转换为时间戳格式,而不是日期和时间格式
  • XSD<xs:enumeration value=“half-down”/>中的枚举值具有空间将转换为“half_UP”而不是“half-UP”

按照步骤自动生成类文件并解决以上两个问题:-

Steps

1.添加Gradle任务以生成类

在build.gradle中添加jaxb gradle任务,其中您指定了以下内容:-

  • 生成类的目标destDir和包名,例如,JAXB在目录src/main/generated sources和package com.example.JAXB中生成类
  • xsd文件位置,例如JAXB从模式文件src/main/resources/schema/schema.xsd生成java类。
  • binding.xjb文件位置,对于例如JAXB,使用绑定文件src/main/resources/JAXB/bindings.xjb

build.gradle

configurations {
    jaxb
}

// Dependencies to be used by "jaxb" task
dependencies {
    jaxb(
        'com.sun.xml.bind:jaxb-xjc:2.3.1',
        'com.sun.xml.bind:jaxb-impl:2.3.1',
        'org.glassfish.jaxb:jaxb-runtime:2.3.1',
        'org.jvnet.jaxb2_commons:jaxb2-basics:0.12.0'
    )
}

// JAXB task definition
task jaxb {
    def generatedResouces = "src/main/generated-sources"
    def jaxbTargetDir = file(generatedResouces)
    jaxbTargetDir.deleteDir()

    doLast {
        jaxbTargetDir.mkdirs()
        ant.taskdef(name: 'xjc', classname: 'com.sun.tools.xjc.XJCTask', classpath: configurations.jaxb.asPath)
        ant.jaxbTargetDir = jaxbTargetDir
        ant.xjc(destDir: '${jaxbTargetDir}', package: 'com.example.jaxb', extension: true){
            schema(dir: "src/main/resources/schema", includes: "schema.xsd")
            binding(dir: "src/main/resources/jaxb", includes: "bindings.xjb")
            arg(line: '-XenumValue')
        }
    }
}

// Add generated classes directory to source
sourceSets.main.java.srcDirs += 'src/main/generated-sources'

// Run jaxb task before compile Java classes
compileJava.dependsOn jaxb

2.从多个XSD模式文件生成类

如果需要从不同包中的多个XML schema.xsd文件生成类,可以添加多个ant.xjc是这样的:-

ant.xjc(destDir: '${jaxbTargetDir}', package: 'com.example.jaxb.schema1', extension: true){
    schema(dir: "src/main/resources/schema", includes: "schema1.xsd")
    binding(dir: "src/main/resources/jaxb", includes: "bindings.xjb")
    arg(line: '-XenumValue')
}
ant.xjc(destDir: '${jaxbTargetDir}', package: 'com.example.jaxb.schema2', extension: true){
    schema(dir: "src/main/resources/schema", includes: "schema2.xsd")
    binding(dir: "src/main/resources/jaxb", includes: "bindings.xjb")
    arg(line: '-XenumValue')
}

它将从包com.example.jaxb中的schema1.xsd生成类。包com.example.jaxb.schema2中的schema1和schema2.xsd

修复日期和时间格式问题

AXB将xs:time、xs:date和xs:dateTime映射到javax.xml.datatype。默认情况下为XMLGregorianCalendar。

XMLGregorianCalendar缺乏底层数据类型的语义:

  • 它缺少关于这是时间、日期还是日期时间的信息
  • 它缺少关于该值是本地日期/时间还是与特定时区偏移量相关的信息。
  • 它是可变的

为了避免这种情况,我们希望JAXB映射:-

  • xs:time to java.time.LocalTime
  • xs:date to java.time.LocalDate
  • xs:dateTime to java.time.LocalDateTime

为此,我们需要做两件事,首先为例如com.example.xml.adapter创建适配器类。然后告诉JAXB通过绑定bindingsxjb文件。

TimeAdapter.java

package com.example.xml.adapter;

public class TimeAdapter extends XmlAdapter<String, LocalTime> {

    @Override
    public LocalTime unmarshal(String v) {
        if (Objects.nonNull(v)) {
            try {
                return LocalTime.parse(v);
            } catch (DateTimeParseException e) {
                throw new RuntimeException("Failed to parse time: " + v, e);
            }
        }
        return null;
    }

    @Override
    public String marshal(LocalTime v) {
        if (Objects.nonNull(v)) {
            return v.format(DateTimeFormatter.ISO_TIME);
        }
        return null;
    }
}

bindings.xjb

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<jaxb:bindings version="2.1"
               xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
               xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://java.sun.com/xml/ns/jaxb http://java.sun.com/xml/ns/jaxb/bindingschema_2_0.xsd">

    <jaxb:globalBindings typesafeEnumMaxMembers="2000">
        <xjc:serializable uid="-1"/>
        <xjc:javaType xmlType="xs:date"
                      name="java.time.LocalDate"
                      adapter="com.example.xml.adapter.DateAdapter"/>
        <xjc:javaType xmlType="xs:time"
                      name="java.time.LocalTime"
                      adapter="com.example.xml.adapter.TimeAdapter"/>
        <xjc:javaType xmlType="xs:dateTime"
                      name="java.time.LocalDateTime"
                      adapter="com.example.xml.adapter.DateTimeAdapter"/>
    </jaxb:globalBindings>
</jaxb:bindings>

就这样!现在,生成的类将具有映射到Java时间包的时间、日期和日期时间。

3.修复枚举值问题

首先看问题陈述,下面是xsd模式中枚举的示例:-

schema.xsd

<xs:simpleType name = "roundingDirection">
    <xs:restriction base = "xs:string">
        <xs:enumeration value = "up"/>
        <xs:enumeration value = "half up"/>
        <xs:enumeration value = "down"/>
        <xs:enumeration value = "half down"/>
        <xs:enumeration value = "nearest"/>
    </xs:restriction>
</xs:simpleType>

JAXB从模schema.xsd文件生成枚举类:-

RoundingDirection.java

public enum RoundingDirection {

    @XmlEnumValue("up")
    UP("up"),
    @XmlEnumValue("half up")
    HALF_UP("half up"),
    @XmlEnumValue("down")
    DOWN("down"),
    @XmlEnumValue("half down")
    HALF_DOWN("half down"),
    @XmlEnumValue("nearest")
    NEAREST("nearest");
}

使用上面生成的enum类,Jackson默认转换以下XML:-

AccountSummary.xml

<?xml version="1.0" encoding="UTF-8" ?>
<accountSummary>
    <interest rounding = "half up">27.55</interest>
</accountSummary>

要遵循json:-

AccountSummary.json

{
  "interest" : {
    "value" : 27.55,
    "rounding" : "HALF_UP"
  }
}

Jackson在转换过程中默认使用枚举类的name()方法。如果我们希望在转换中使用枚举值,则需要自定义反序列化器。

我们需要做两件事来解决所有生成的枚举类:-

  • 首先,我们将使用org.jvnet.jaxb2_commons:jaxb2基础,并在jaxb gradle任务中传递参数arg(行:'-XenumValue')。所有生成的枚举类都实现了EnumValue类。
  • 其次,我们为EnumValue编写自定义反序列化器,以便在反序列化为json时使用EnumValue()而不是默认名称()。在ObjectMapper中注册此序列化程序。
public class EnumValueDeserializer extends JsonSerializer<EnumValue> {

    @Override
    public void serialize(EnumValue value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        gen.writeString(value.enumValue().toString());
    }
}
ObjectMapper objectMapper = new ObjectMapper();

SimpleModule module = new SimpleModule();
module.addSerializer(EnumValue.class, new EnumValueDeserializer());
objectMapper.registerModule(module);

objectMapper.registerModule(new JavaTimeModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

 

结论

github/springboot.xml下载本文示例的完整源代码

 

原文:Convert XSD Schema to JSON using JAXB

 

 

李白
关注 私信
文章
12
关注
0
粉丝
0