В этой статье я продемонстрирую один из подходов к созданию схемы JSON из схемы XML (XSD). Представляя обзор подхода к созданию JSON-схемы из XML-схемы, эта статья также демонстрирует использование реализации JAXB (версия 2.2.12-b150331.1824 xjc в комплекте с JDK 9 [build 1.9.0-ea-b68]) и реализации связывания JSON / Java ( Джексон 2.5.4).
Шаги этого подхода для генерации JSON-схемы из XSD можно обобщить следующим образом:
- Примените компилятор JAXB xjc для генерации классов Java из XML-схемы (XSD).
- Примените Джексона для генерации JSON-схемы из сгенерированных JAXB Java-классов.
Генерация Java-классов из XSD с помощью JAXB’s xjc
Для целей этого обсуждения я буду использовать простой Food.xsd использованный в моем предыдущем сообщении в блоге «Нюанс JAXB: строка в сравнении с перечислением из перечисленной строки XSD с ограниченным доступом . Для удобства я воспроизвел здесь эту простую схему без комментариев XML, относящихся к предыдущему сообщению в блоге:
Food.xsd
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
<?xml version="1.0" encoding="utf-8"?> elementFormDefault="qualified" attributeFormDefault="unqualified"> <xs:element name="Food"> <xs:complexType> <xs:sequence> <xs:element name="Vegetable" type="dustin:Vegetable" /> <xs:element ref="dustin:Fruit" /> <xs:element name="Dessert" type="dustin:Dessert" /> </xs:sequence> </xs:complexType> </xs:element> <xs:simpleType name="Vegetable"> <xs:restriction base="xs:string"> <xs:enumeration value="Carrot"/> <xs:enumeration value="Squash"/> <xs:enumeration value="Spinach"/> <xs:enumeration value="Celery"/> </xs:restriction> </xs:simpleType> <xs:element name="Fruit"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:enumeration value="Watermelon"/> <xs:enumeration value="Apple"/> <xs:enumeration value="Orange"/> <xs:enumeration value="Grape"/> </xs:restriction> </xs:simpleType> </xs:element> <xs:simpleType name="Dessert"> <xs:restriction base="xs:string"> <xs:enumeration value="Pie"/> <xs:enumeration value="Cake"/> <xs:enumeration value="Ice Cream"/> </xs:restriction> </xs:simpleType></xs:schema> |
Легко использовать инструмент командной строки xjc, предоставляемый реализацией JAXB, предоставленной JDK, для генерации классов Java, соответствующих этому XSD. Следующий снимок экрана показывает этот процесс с помощью команды:
xjc -d jaxb. \ Food.xsd
Эта простая команда генерирует классы Java, соответствующие предоставленному Food.xsd и помещает эти классы в указанный подкаталог «jaxb».
Генерация JSON из классов, сгенерированных JAXB, с Джексоном
Теперь, когда сгенерированные JAXB классы доступны, Джексон может быть применен к этим классам для генерации JSON из классов Java. На главной странице портала Джексон описан как «многоцелевая библиотека Java для обработки», которая «вдохновлена качеством и разнообразием инструментов XML, доступных для платформы Java». Существование Jackson и аналогичных сред и библиотек, по-видимому, является одной из причин того, что Oracle отказалась от JEP 198 («Облегченный JSON API») с Java SE 9 . [Стоит отметить, что Java EE 7 уже имеет встроенную поддержку JSON с его реализацией JSR 353 («Java API для обработки JSON»), которая не связана с JEP 198).]
Одним из первых шагов применения Jackson для генерации JSON из наших сгенерированных JAXB Java-классов является получение и настройка экземпляра класса Jackson ObjectMapper . Один из подходов для достижения этой цели показан в следующем листинге кода.
Получение и настройка Jackson ObjectMapper для сериализации / десериализации JAXB
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
/** * Create instance of ObjectMapper with JAXB introspector * and default type factory. * * @return Instance of ObjectMapper with JAXB introspector * and default type factory. */private ObjectMapper createJaxbObjectMapper(){ final ObjectMapper mapper = new ObjectMapper(); final TypeFactory typeFactory = TypeFactory.defaultInstance(); final AnnotationIntrospector introspector = new JaxbAnnotationIntrospector(typeFactory); // make deserializer use JAXB annotations (only) mapper.getDeserializationConfig().with(introspector); // make serializer use JAXB annotations (only) mapper.getSerializationConfig().with(introspector); return mapper;} |
Приведенный выше листинг кода демонстрирует получение экземпляра Jackson ObjectMapper и его настройку на использование фабрики типов по умолчанию и интроспектора JAXB-ориентированных аннотаций.
С ObjectMapper и соответствующим образом сконфигурированным Jackson ObjectMapper экземпляр ObjectMapper легко использовать для генерации JSON из сгенерированных классов JAXB. Один из способов сделать это с помощью устаревшего класса Джексона JsonSchema продемонстрирован в следующем листинге кода.
Генерация JSON из классов Java с устаревшим классом com.fasterxml.jackson.databind.jsonschema.JsonSchema
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
/** * Write JSON Schema to standard output based upon Java source * code in class whose fully qualified package and class name * have been provided. * * @param mapper Instance of ObjectMapper from which to * invoke JSON schema generation. * @param fullyQualifiedClassName Name of Java class upon * which JSON Schema will be extracted. */private void writeToStandardOutputWithDeprecatedJsonSchema( final ObjectMapper mapper, final String fullyQualifiedClassName){ try { final JsonSchema jsonSchema = mapper.generateJsonSchema(Class.forName(fullyQualifiedClassName)); out.println(jsonSchema); } catch (ClassNotFoundException cnfEx) { err.println("Unable to find class " + fullyQualifiedClassName); } catch (JsonMappingException jsonEx) { err.println("Unable to map JSON: " + jsonEx); }} |
Код в приведенном выше листинге создает экземпляр класса для предоставленного Java-класса (класс Food высшего уровня, сгенерированный компилятором JAXB xjc в моем примере) и передает эту ссылку на класс, сгенерированный JAXB, в generateJaSerma generateJsonSchema (Class < ?>) метод. Устаревшая реализация класса toString () класса JsonSchema очень полезна и позволяет легко выписывать JSON, сгенерированный из классов, сгенерированных JAXB.
Для целей этой демонстрации я предоставляю драйвер демонстрации в качестве основной (String []) функции. Эта функция и весь класс до этого момента (включая методы, показанные выше) представлены в следующем листинге кода.
JsonGenerationFromJaxbClasses.java, версия 1
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
package dustin.examples.jackson;import com.fasterxml.jackson.databind.AnnotationIntrospector;import com.fasterxml.jackson.databind.JsonMappingException;import com.fasterxml.jackson.databind.ObjectMapper;import com.fasterxml.jackson.databind.type.TypeFactory;import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector;import com.fasterxml.jackson.databind.jsonschema.JsonSchema;import static java.lang.System.out;import static java.lang.System.err;/** * Generates JavaScript Object Notation (JSON) from Java classes * with Java API for XML Binding (JAXB) annotations. */public class JsonGenerationFromJaxbClasses{ /** * Create instance of ObjectMapper with JAXB introspector * and default type factory. * * @return Instance of ObjectMapper with JAXB introspector * and default type factory. */ private ObjectMapper createJaxbObjectMapper() { final ObjectMapper mapper = new ObjectMapper(); final TypeFactory typeFactory = TypeFactory.defaultInstance(); final AnnotationIntrospector introspector = new JaxbAnnotationIntrospector(typeFactory); // make deserializer use JAXB annotations (only) mapper.getDeserializationConfig().with(introspector); // make serializer use JAXB annotations (only) mapper.getSerializationConfig().with(introspector); return mapper; } /** * Write out JSON Schema based upon Java source code in * class whose fully qualified package and class name have * been provided. * * @param mapper Instance of ObjectMapper from which to * invoke JSON schema generation. * @param fullyQualifiedClassName Name of Java class upon * which JSON Schema will be extracted. */ private void writeToStandardOutputWithDeprecatedJsonSchema( final ObjectMapper mapper, final String fullyQualifiedClassName) { try { final JsonSchema jsonSchema = mapper.generateJsonSchema(Class.forName(fullyQualifiedClassName)); out.println(jsonSchema); } catch (ClassNotFoundException cnfEx) { err.println("Unable to find class " + fullyQualifiedClassName); } catch (JsonMappingException jsonEx) { err.println("Unable to map JSON: " + jsonEx); } } /** * Accepts the fully qualified (full package) name of a * Java class with JAXB annotations that will be used to * generate a JSON schema. * * @param arguments One argument expected: fully qualified * package and class name of Java class with JAXB * annotations. */ public static void main(final String[] arguments) { if (arguments.length < 1) { err.println("Need to provide the fully qualified name of the highest-level Java class with JAXB annotations."); System.exit(-1); } final JsonGenerationFromJaxbClasses instance = new JsonGenerationFromJaxbClasses(); final String fullyQualifiedClassName = arguments[0]; final ObjectMapper objectMapper = instance.createJaxbObjectMapper(); instance.writeToStandardOutputWithDeprecatedJsonSchema(objectMapper, fullyQualifiedClassName); }} |
Чтобы запустить этот относительно общий код для классов Java, сгенерированных xjc JAXB на основе Food.xsd , мне нужно предоставить полное имя пакета и имя класса сгенерированного класса самого высокого уровня. В данном случае это com.blogspot.marxsoftware.foodxml.Food (имя пакета основано на пространстве имен XSD, поскольку я явно не переопределял его при запуске xjc ). Когда я запускаю приведенный выше код с этим полностью определенным именем класса и с классами JAXB и библиотеками Джексона на пути к классам, я вижу следующий JSON, записанный в стандартный вывод.
Сгенерированный JSON
|
1
|
{"type":"object","properties":{"vegetable":{"type":"string","enum":["CARROT","SQUASH","SPINACH","CELERY"]},"fruit":{"type":"string"},"dessert":{"type":"string","enum":["PIE","CAKE","ICE_CREAM"]}}} |
Люди (включая многих разработчиков) предпочитают более красивую печать, чем то, что было показано для созданного JSON. Мы можем настроить реализацию метода демонстрационного класса writeToStandardOutputWithDeprecatedJsonSchema(ObjectMapper, String) как показано ниже, чтобы выписать JSON с отступом, который лучше отражает его иерархическую природу. Этот модифицированный метод показан ниже.
Модифицированный writeToStandardOutputWithDeprecatedJsonSchema (ObjectMapper, String) для записи с отступом JSON
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
/** * Write out indented JSON Schema based upon Java source * code in class whose fully qualified package and class * name have been provided. * * @param mapper Instance of ObjectMapper from which to * invoke JSON schema generation. * @param fullyQualifiedClassName Name of Java class upon * which JSON Schema will be extracted. */private void writeToStandardOutputWithDeprecatedJsonSchema( final ObjectMapper mapper, final String fullyQualifiedClassName){ try { final JsonSchema jsonSchema = mapper.generateJsonSchema(Class.forName(fullyQualifiedClassName)); out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonSchema)); } catch (ClassNotFoundException cnfEx) { err.println("Unable to find class " + fullyQualifiedClassName); } catch (JsonMappingException jsonEx) { err.println("Unable to map JSON: " + jsonEx); } catch (JsonProcessingException jsonEx) { err.println("Unable to process JSON: " + jsonEx); }} |
Когда я снова запускаю демонстрационный класс с этим измененным методом, вывод JSON выглядит более эстетично:
Сгенерированный JSON с иерархией связи отступов
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
{ "type" : "object", "properties" : { "vegetable" : { "type" : "string", "enum" : [ "CARROT", "SQUASH", "SPINACH", "CELERY" ] }, "fruit" : { "type" : "string" }, "dessert" : { "type" : "string", "enum" : [ "PIE", "CAKE", "ICE_CREAM" ] } }} |
Я использовал Джексон 2.5.4 в этом посте. Класс com.fasterxml.jackson. databind .jsonschema.JsonSchema com.fasterxml.jackson. databind .jsonschema.JsonSchema устарела в этой версии с комментарием: «Начиная с версии 2.2, мы рекомендуем использовать внешний модуль генератора JSON Schema» . Учитывая это, я теперь рассмотрим использование нового предпочтительного подхода (подход к схеме JSON JSON ).
Наиболее значительным изменением является использование класса JsonSchema в пакете com.fasterxml.jackson.module.jsonSchema , а не использование класса JsonSchema в пакете com.fasterxml.jackson.databind.jsonschema . Подходы для получения экземпляров этих различных версий классов JsonSchema также различны. Следующий листинг кода демонстрирует использование более нового предпочтительного подхода для генерации JSON из классов Java.
Использование Джексона новее и предпочтительнее com.fasterxml.jackson.module.jsonSchema.JsonSchema
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
/** * Write out JSON Schema based upon Java source code in * class whose fully qualified package and class name have * been provided. This method uses the newer module JsonSchema * class that replaces the deprecated databind JsonSchema. * * @param fullyQualifiedClassName Name of Java class upon * which JSON Schema will be extracted. */private void writeToStandardOutputWithModuleJsonSchema( final String fullyQualifiedClassName){ final SchemaFactoryWrapper visitor = new SchemaFactoryWrapper(); final ObjectMapper mapper = new ObjectMapper(); try { mapper.acceptJsonFormatVisitor(mapper.constructType(Class.forName(fullyQualifiedClassName)), visitor); final com.fasterxml.jackson.module.jsonSchema.JsonSchema jsonSchema = visitor.finalSchema(); out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonSchema)); } catch (ClassNotFoundException cnfEx) { err.println("Unable to find class " + fullyQualifiedClassName); } catch (JsonMappingException jsonEx) { err.println("Unable to map JSON: " + jsonEx); } catch (JsonProcessingException jsonEx) { err.println("Unable to process JSON: " + jsonEx); }} |
В следующей таблице сравнивается использование двух классов Jackson JsonSchema бок о бок с устаревшим подходом, показанным ранее слева (немного адаптированный для этого сравнения), и рекомендуемым более новым подходом справа. Оба генерируют один и тот же вывод для одного и того же данного Java-класса, из которого должен быть написан JSON.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
/** * Write out JSON Schema based upon Java source code in * class whose fully qualified package and class name have * been provided. This method uses the deprecated JsonSchema * class in the "databind.jsonschema" package * {@see com.fasterxml.jackson.databind.jsonschema}. * * @param fullyQualifiedClassName Name of Java class upon * which JSON Schema will be extracted. */private void writeToStandardOutputWithDeprecatedDatabindJsonSchema( final String fullyQualifiedClassName){ final ObjectMapper mapper = new ObjectMapper(); try { final com.fasterxml.jackson.databind.jsonschema.JsonSchema jsonSchema = mapper.generateJsonSchema(Class.forName(fullyQualifiedClassName)); out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonSchema)); } catch (ClassNotFoundException cnfEx) { err.println("Unable to find class " + fullyQualifiedClassName); } catch (JsonMappingException jsonEx) { err.println("Unable to map JSON: " + jsonEx); } catch (JsonProcessingException jsonEx) { err.println("Unable to process JSON: " + jsonEx); }} |
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
/** * Write out JSON Schema based upon Java source code in * class whose fully qualified package and class name have * been provided. This method uses the newer module JsonSchema * class that replaces the deprecated databind JsonSchema. * * @param fullyQualifiedClassName Name of Java class upon * which JSON Schema will be extracted. */private void writeToStandardOutputWithModuleJsonSchema( final String fullyQualifiedClassName){ final SchemaFactoryWrapper visitor = new SchemaFactoryWrapper(); final ObjectMapper mapper = new ObjectMapper(); try { mapper.acceptJsonFormatVisitor(mapper.constructType(Class.forName(fullyQualifiedClassName)), visitor); final com.fasterxml.jackson.module.jsonSchema.JsonSchema jsonSchema = visitor.finalSchema(); out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonSchema)); } catch (ClassNotFoundException cnfEx) { err.println("Unable to find class " + fullyQualifiedClassName); } catch (JsonMappingException jsonEx) { err.println("Unable to map JSON: " + jsonEx); } catch (JsonProcessingException jsonEx) { err.println("Unable to process JSON: " + jsonEx); }} |
В этом посте блога показаны два подхода, использующие разные версии классов с именем JsonSchema предоставленные Джексоном для написания JSON на основе классов Java, сгенерированных из XSD с помощью xjc JAXB. Общий процесс, продемонстрированный в этом посте, представляет собой один из подходов к созданию JSON-схемы из XML-схемы.
| Ссылка: | Создание схемы JSON из XSD с JAXB и Джексоном от нашего партнера по JCG Дастина Маркса в блоге Inspired by Actual Events . |
