Я использую общую модель JAXB для JAX-WS (Metro) и JAX-RS (Джерси). У меня есть следующий фрагмент запроса:
<xs:element name="CreatedProjFolders">
<xs:complexType>
<xs:sequence>
<xs:element name="CreatedProjFolder" type="tns:CreatedProjFolder" minOccurs="0"
maxOccurs="unbounded" />
</xs:sequence>
<xs:attribute name="parentItemId" type="tns:itemId" use="required" />
</xs:complexType>
</xs:element>
<xs:complexType name="CreateProjFolder">
<xs:attribute name="itemId" type="tns:itemId" use="required" />
...
</xs:complexType>
XJC сгенерировал этот класс:
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"createProjFolders"
})
@XmlRootElement(name = "CreateProjFolders")
public class CreateProjFolders {
@XmlElement(name = "CreateProjFolder", required = true)
@NotNull
@Size(min = 1)
@Valid
// Name has been pluralized with JAXB bindings file
protected List<CreateProjFolder> createProjFolders;
@XmlAttribute(name = "parentItemId", required = true)
@NotNull
@Size(max = 128)
protected String parentItemId;
...
}
Соответствующий JSON POST должен выглядеть так:
{"parentItemId":"P5J00142301", "createProjFolders":[
{"itemId":"bogus"}
]}
но на самом деле должно выглядеть так:
{"parentItemId":"P5J00142301", "CreateProjFolder":[
{"itemId":"bogus"}
]}
Как можно переименовать имя свойства для JSON, только похожего на имя в Java (protected List<CreateProjFolder> createProjFolders
)?
После чтения сообщения Блейза и блога мне потребовалось два дня, чтобы придумать рабочее решение. Прежде всего, текущее состояние MOXyJsonProvider
и ConfigurableMoxyJsonProvider
делает его непринужденным, чтобы он работал и никогда не был разработан для этого.
Мой первый тест состоял в том, чтобы сделать чистую комнату, которая полностью отделена от Джерси и работает main
образом.
Вот main
метод:
public static void main(String[] args) throws JAXBException {
Map<String, Object> props = new HashMap<>();
InputStream importMoxyBinding = MOXyTest.class
.getResourceAsStream("/json-binding.xml");
List<InputStream> moxyBindings = new ArrayList<>();
moxyBindings.add(importMoxyBinding);
props.put(JAXBContextProperties.OXM_METADATA_SOURCE, moxyBindings);
props.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
props.put(JAXBContextProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON);
JAXBContext jc = JAXBContext.newInstance("my.package",
CreateProjFolders.class.getClassLoader(), props);
Unmarshaller um = jc.createUnmarshaller();
InputStream json = MOXyTest.class
.getResourceAsStream("/CreateProjFolders.json");
Source source = new StreamSource(json);
JAXBElement<CreateProjFolders> create = um.unmarshal(source, CreateProjFolders.class);
CreateProjFolders folders = create.getValue();
System.out.printf("Used JAXBContext: %s%n", jc);
System.out.printf("Unmarshalled structure: %s%n", folders);
Marshaller m = jc.createMarshaller();
m.setProperty(MarshallerProperties.INDENT_STRING, " ");
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
System.out.print("Marshalled structure: ");
m.marshal(folders, System.out);
}
Здесь json-binding.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
package-name="my.package"
xml-mapping-metadata-complete="false">
<xml-schema namespace="urn:namespace"
element-form-default="QUALIFIED" />
<java-types>
<java-type name="CreateProjFolders">
<xml-root-element />
<java-attributes>
<xml-element java-attribute="projFolders" name="createProjFolders" />
</java-attributes>
</java-type>
<java-type name="CreateProjFolder">
<java-attributes>
<xml-element java-attribute="access" name="access" />
</java-attributes>
</java-type>
<java-type name="Access">
<java-attributes>
<xml-element java-attribute="productionSites" name="productionSites" />
</java-attributes>
</java-type>
</java-types>
</xml-bindings>
и примерный входной файл:
{"parentItemId":"some-id",
"createProjFolders":[
{"objectNameEn":"bogus", "externalProjectId":"123456",
"access":{"productionSites":["StackOverflow"], "user":"michael-o"}}
]
}
Совершенствование и сортировка работают безупречно. Теперь, как заставить его работать с Джерси? Вы не можете, потому что вы не можете передавать свойства JAXBContext.
Вам нужно скопировать MOXy MOXyJsonProvider
и весь источник Jersey Media MOXy, за исключением XML-материала в новый проект Maven из-за функции AutoDiscoverable
. Этот пакет заменит исходную зависимость.
Примените следующие патчи. Патчи не идеальны и могут быть импровизированы, потому что некоторый код дублируется, поэтому избыточен, но это может быть сделано в билет позже.
Теперь подтвердите, что в вашем Application.class
:
InputStream importMoxyBinding = MyApplication.class
.getResourceAsStream("/json-binding.xml");
List<InputStream> moxyBindings = new ArrayList<>();
moxyBindings.add(importMoxyBinding);
final MoxyJsonConfig jsonConfig = new MoxyJsonConfig();
jsonConfig.setOxmMedatadataSource(moxyBindings);
ContextResolver<MoxyJsonConfig> jsonConfigResolver = jsonConfig.resolver();
register(jsonConfigResolver);
Теперь попробуйте. После нескольких вызовов на разных моделях вы увидите JAXBExceptions
с "преждевременным концом файла". Вы спросите, почему?! Причина в том, что MOXyJsonProvider
создает и кэширует JAXBContexts
каждого класса домена, а не для каждого пакета, что означает, что ваш поток ввода читается несколько раз, но уже был закрыт после первого чтения. Ты потерялся. Вам нужно сбросить поток, но не можете изменить внутренние кишки MOXy. Вот простое решение для этого:
public class ResetOnCloseInputStream extends BufferedInputStream {
public ResetOnCloseInputStream(InputStream is) {
super(is);
super.mark(Integer.MAX_VALUE);
}
@Override
public void close() throws IOException {
super.reset();
}
}
и замените свой Application.class
для
moxyBindings.add(new ResetOnCloseInputStream(importMoxyBinding));
После того, как вы почувствовали боль в заднице, наслаждайтесь волшебством!
Заключительные слова:
OXM_METADATA_SOURCE
единственный, кто хочет передать OXM_METADATA_SOURCE
. Шутки в сторону? Когда MOXy используется как ваш поставщик JSON-привязки, ключи JSON будут такими же, как и specfieid в аннотациях @XmlElement
. Например, если у вас есть:
@XmlElement(name = "CreateProjFolder", required = true)
protected List<CreateProjFolder> createProjFolders;
Ты получишь:
{"parentItemId":"P5J00142301", "CreateProjFolder":[
{"itemId":"bogus"}
]}
Если вам нужны разные имена в JSON, чем в XML, вы можете использовать MOXy для внешних метаданных сопоставления, чтобы переопределить то, что было указано в аннотациях:
OXM_KEY
для этого маршалинга и не касаться маршаллинга XML?