I'm trying to use MOXy JAXB to unmarshal some JSON/XML in an Apache CFX project. The following request gives me trouble:
{"CC.ConsumerPersistAppDataRequest" : {
"SessionId" : "42",
"AppData" : [
{"AppDatum" : {
"TimeStamp" : 1384548486,
"Value" : "Some kind of String would go here"}},
{"AppDatum" : {
"TimeStamp" : 1384548578,
"Value" : "I hope I am understanding this format correctly!"}},
{"AppDatum" : {
"TimeStamp" : 1384549696,
"Value" : "One more time for the road..."}}],
"MetaDataTags" : ["dumb", "dummy", "data"]}
}
Here, AppData is unmarshalled as a List<AppDatum>, however the list only contains the last element. Notably, the "MetaDataTags" element is correctly unmarshalled as a List<String> of size 3.
Surprisingly, the following request can be submitted with the same result, despite the fact the "AppData" should expect a List<AppDatum> (I can't help but feel this is related):
{"CC.ConsumerPersistAppDataRequest" : {
"SessionId" : "42",
"AppData" : {
"AppDatum" : {
"TimeStamp" : 1384548486,
"Value" : "Some kind of String would go here"
}
}
}}
This XML equivalent (or what I see to be the XML equivalent) is parsed as expected:
<?xml version="1.0" encoding="UTF-8"?>
<cc:ConsumerPersistAppDataRequest xmlns:cc="http://org/alg/ari/pnd/introspect/webservices/consumercomms">
<SessionId>42</SessionId>
<AppData>
<AppDatum>
<TimeStamp>1384548486</TimeStamp>
<Value>Some kind of String would go here</Value>
</AppDatum>
<AppDatum>
<TimeStamp>1384548578</TimeStamp>
<Value>I hope I am understanding this format correctly!</Value>
</AppDatum>
<AppDatum>
<TimeStamp>1384549696</TimeStamp>
<Value>One more time for the road...</Value>
</AppDatum>
</AppData>
<MetaDataTags>dumb dummy data</MetaDataTags>
</cc:ConsumerPersistAppDataRequest>
The JAXB class in question (generated by XJC, comments elided):
package org.alg.ari.pnd.introspect.webservices.consumercomms;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlList;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"sessionId",
"appData",
"metaDataTags"
})
#XmlRootElement(name = "ConsumerPersistAppDataRequest")
public class ConsumerPersistAppDataRequest {
#XmlElement(name = "SessionId", required = true)
protected String sessionId;
#XmlElement(name = "AppData", required = true)
protected ConsumerPersistAppDataRequest.AppData appData;
#XmlList
#XmlElement(name = "MetaDataTags", required = true)
protected List<String> metaDataTags;
public String getSessionId() {
return sessionId;
}
public void setSessionId(String value) {
this.sessionId = value;
}
public ConsumerPersistAppDataRequest.AppData getAppData() {
return appData;
}
public void setAppData(ConsumerPersistAppDataRequest.AppData value) {
this.appData = value;
}
public List<String> getMetaDataTags() {
if (metaDataTags == null) {
metaDataTags = new ArrayList<String>();
}
return this.metaDataTags;
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"appDatum"
})
public static class AppData {
#XmlElement(name = "AppDatum", required = true)
protected List<ConsumerPersistAppDataRequest.AppData.AppDatum> appDatum;
public List<ConsumerPersistAppDataRequest.AppData.AppDatum> getAppDatum() {
if (appDatum == null) {
appDatum = new ArrayList<ConsumerPersistAppDataRequest.AppData.AppDatum>();
}
return this.appDatum;
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"timeStamp",
"value"
})
public static class AppDatum {
#XmlElement(name = "TimeStamp")
protected long timeStamp;
#XmlElement(name = "Value", required = true)
protected String value;
public long getTimeStamp() {
return timeStamp;
}
public void setTimeStamp(long value) {
this.timeStamp = value;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
}
}
and my MoxyJsonProvider bean
<bean id="moxy-json-provider"
class="org.eclipse.persistence.jaxb.rs.MOXyJsonProvider">
<property name="attributePrefix" value="#" />
<property name="formattedOutput" value="true" /> <!-- set to false for production! -->
<property name="includeRoot" value="true" />
<property name="marshalEmptyCollections" value="false" />
<property name="valueWrapper" value="$" />
<property name="namespacePrefixMapper" ref="json-ns-mapper" />
</bean>
Any idea what could be causing this issue? Is this a bug in the 2.5.1 Release of MOXy? Or is there a "switch" somewhere that I need to set?
Based on your mapping, the folloiwng would be the expected JSON document:
{
"CC.ConsumerPersistAppDataRequest" : {
"SessionId" : "42",
"AppData" : {
"AppDatum" : [ {
"TimeStamp" : 1384548486,
"Value" : "Some kind of String would go here"
}, {
"TimeStamp" : 1384548578,
"Value" : "I hope I am understanding this format correctly!"
}, {
"TimeStamp" : 1384549696,
"Value" : "One more time for the road..."
} ]
},
"MetaDataTags" : "dumb dummy data"
}
}
The following is the standalone example I wrote based on yours to read in the XML and output the corresponding JSON:
import java.io.File;
import java.util.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.MarshallerProperties;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(ConsumerPersistAppDataRequest.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum20056524/input.xml");
ConsumerPersistAppDataRequest result = (ConsumerPersistAppDataRequest) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(MarshallerProperties.MEDIA_TYPE, "application/json");
marshaller.setProperty(MarshallerProperties.JSON_ATTRIBUTE_PREFIX, "#");
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, true);
marshaller.setProperty(MarshallerProperties.JSON_MARSHAL_EMPTY_COLLECTIONS, false);
marshaller.setProperty(MarshallerProperties.JSON_VALUE_WRAPPER, "$");
Map<String, String> jsonNsMapper = new HashMap<String, String>(1);
jsonNsMapper.put("http://org/alg/ari/pnd/introspect/webservices/consumercomms", "CC");
marshaller.setProperty(MarshallerProperties.NAMESPACE_PREFIX_MAPPER, jsonNsMapper);
marshaller.marshal(result, System.out);
}
}
Related
I have a json file which is having some template already defined say:
{
"ownerContext": 0,
"databaseObjects": [
{
"objectName": "**CT_OPERATOR_ROUTE**",
"dateColumnToFilter": "PRODUCTION_DAY",
"whereCondition": "CREATED_BY IS NULL",
"primaryKeyColumnList": null,
"ignoreColumnList": [
"DAYTIME"
],
"includeAuditColumnList": [
"CREATED_DATE",
"CREATED_BY"
],
"generateAnalysis": true,
"analysisStrategy": "FULL_DETAILS"
},
{
"objectName": "**DT_CT_EQPM_EQPOTHER**",
"dateColumnToFilter": null,
"whereCondition": null,
"primaryKeyColumnList": null,
"ignoreColumnList": [
"DAYTIME"
]
...
}
suppose I have 1000 objects to get into the template then I have to to copy paste every single time,is there any way i can fetch object name from my .txt file and place it as value for object name
My suggestion is:
Read txt file, save object name in a list, maybe List<String>
Create POJO to map json object
Update object name to POJO using for-loop
Example:
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
public class Demo {
public static void main(String[] args) throws IOException {
List<String> objectNames = getObjectNames();
Context context = updateObjectName(objectNames);
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(context);
System.out.println(json);
}
private static List<String> getObjectNames() throws IOException {
Path path = Path.of("api/src/main/resources/test.txt");
return Files.readAllLines(path);
}
public static Context updateObjectName(List<String> objectNames) {
Context context = new Context();
context.setOwnerContext(0);
List<DatabaseObject> databaseObjects = new ArrayList<>();
for (String name : objectNames) {
DatabaseObject dbObject = new DatabaseObject();
dbObject.setObjectName(name);
databaseObjects.add(dbObject);
}
context.setDatabaseObjects(databaseObjects);
return context;
}
#Data
static class Context {
private int ownerContext;
private List<DatabaseObject> databaseObjects;
}
#Data
static class DatabaseObject {
private String objectName;
private String dateColumnToFilter;
}
}
Result
{
"ownerContext" : 0,
"databaseObjects" : [ {
"objectName" : "Object A",
"dateColumnToFilter" : null
}, {
"objectName" : "Object B",
"dateColumnToFilter" : null
}, {
"objectName" : "Object C",
"dateColumnToFilter" : null
} ]
}
For library:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.4</version>
</dependency>
My objective is to read a very complex JSON using Spring Batch. Below is the sample JSON.
{
"order-info" : {
"order-number" : "Test-Order-1"
"order-items" : [
{
"item-id" : "4144769310"
"categories" : [
"ABCD",
"DEF"
],
"item_imag" : "http:// "
"attributes: {
"color" : "red"
},
"dimensions" : {
},
"vendor" : "abcd",
},
{
"item-id" : "88888",
"categories" : [
"ABCD",
"DEF"
],
.......
I understand that I would need to create a Custom ItemReader to parse this JSON.
Kindly provide me some pointers. I am really clueless.
I am now not using CustomItemReader. I am using Java POJOs. My JsonItemReader is as per below:
#Bean
public JsonItemReader<Trade> jsonItemReader() {
ObjectMapper objectMapper = new ObjectMapper();
JacksonJsonObjectReader<Trade> jsonObjectReader =
new JacksonJsonObjectReader<>(Trade.class);
jsonObjectReader.setMapper(objectMapper);
return new JsonItemReaderBuilder<Trade>()
.jsonObjectReader(jsonObjectReader)
.resource(new ClassPathResource("search_data_1.json"))
.name("tradeJsonItemReader")
.build();
}
The exception which I now get is :
java.lang.IllegalStateException: The Json input stream must start with an array of Json objects
From similar posts in this forum I understand that I need to use JsonObjectReader. "You can implement it to read a single json object and use it with the JsonItemReader (either at construction time or using the setter)".
How can I do this either # construction time or using setter? Please share some code snippet for the same.
The delegate of MultiResourceItemReader should still be a JsonItemReader. You just need to use a custom JsonObjectReader with the JsonItemReader instead of JacksonJsonObjectReader. Visually, this would be: MultiResourceItemReader -- delegates to --> JsonItemReader -- uses --> your custom JsonObjectReader.
Could you please share a code snippet for the above?
JacksonJsonItemReader is meant to parse from a root node that is already and array node, so it expects your json to start with '['.
If you desire to parse a complex object - in this case, one that have many parent nodes/properties before it gets to the array - you should write a reader. It is really simple to do it and you can follow JacksonJsonObjectReader's structure. Here follows and example of a generic reader for complex object with respective unit tests.
The unit test
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.springframework.core.io.ByteArrayResource;
import com.example.batch_experiment.dataset.Dataset;
import com.example.batch_experiment.dataset.GenericJsonObjectReader;
import com.example.batch_experiment.json.InvalidArrayNodeException;
import com.example.batch_experiment.json.UnreachableNodeException;
import com.fasterxml.jackson.databind.ObjectMapper;
#RunWith(BlockJUnit4ClassRunner.class)
public class GenericJsonObjectReaderTest {
GenericJsonObjectReader<Dataset> reader;
#Before
public void setUp() {
reader = new GenericJsonObjectReader<Dataset>(Dataset.class, "results");
}
#Test
public void shouldRead_ResultAsRootNode() throws Exception {
reader.open(new ByteArrayResource("{\"result\":{\"results\":[{\"id\":\"a\"}]}}".getBytes()) {});
Assert.assertTrue(reader.getDatasetNode().isArray());
Assert.assertFalse(reader.getDatasetNode().isEmpty());
}
#Test
public void shouldIgnoreUnknownProperty() throws Exception {
String jsonStr = "{\"result\":{\"results\":[{\"id\":\"a\", \"aDifferrentProperty\":0}]}}";
reader.open(new ByteArrayResource(jsonStr.getBytes()) {});
Assert.assertTrue(reader.getDatasetNode().isArray());
Assert.assertFalse(reader.getDatasetNode().isEmpty());
}
#Test
public void shouldIgnoreNullWithoutQuotes() throws Exception {
String jsonStr = "{\"result\":{\"results\":[{\"id\":\"a\",\"name\":null}]}}";
try {
reader.open(new ByteArrayResource(jsonStr.getBytes()) {});
Assert.assertTrue(reader.getDatasetNode().isArray());
Assert.assertFalse(reader.getDatasetNode().isEmpty());
} catch (Exception e) {
Assert.fail(e.getMessage());
}
}
#Test
public void shouldThrowException_OnNullNode() throws Exception {
boolean exceptionThrown = false;
try {
reader.open(new ByteArrayResource("{}".getBytes()) {});
} catch (UnreachableNodeException e) {
exceptionThrown = true;
}
Assert.assertTrue(exceptionThrown);
}
#Test
public void shouldThrowException_OnNotArrayNode() throws Exception {
boolean exceptionThrown = false;
try {
reader.open(new ByteArrayResource("{\"result\":{\"results\":{}}}".getBytes()) {});
} catch (InvalidArrayNodeException e) {
exceptionThrown = true;
}
Assert.assertTrue(exceptionThrown);
}
#Test
public void shouldReadObjectValue() {
try {
reader.setJsonParser(new ObjectMapper().createParser("{\"id\":\"a\"}"));
Dataset dataset = reader.read();
Assert.assertNotNull(dataset);
Assert.assertEquals("a", dataset.getId());
} catch (Exception e) {
Assert.fail(e.getMessage());
}
}
}
And the reader:
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Logger;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.json.JsonObjectReader;
import org.springframework.core.io.Resource;
import com.example.batch_experiment.json.InvalidArrayNodeException;
import com.example.batch_experiment.json.UnreachableNodeException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
/*
* This class follows the structure and functions similar to JacksonJsonObjectReader, with
* the difference that it expects a object as root node, instead of an array.
*/
public class GenericJsonObjectReader<T> implements JsonObjectReader<T>{
Logger logger = Logger.getLogger(GenericJsonObjectReader.class.getName());
ObjectMapper mapper = new ObjectMapper();
private JsonParser jsonParser;
private InputStream inputStream;
private ArrayNode targetNode;
private Class<T> targetType;
private String targetPath;
public GenericJsonObjectReader(Class<T> targetType, String targetPath) {
super();
this.targetType = targetType;
this.targetPath = targetPath;
}
public JsonParser getJsonParser() {
return jsonParser;
}
public void setJsonParser(JsonParser jsonParser) {
this.jsonParser = jsonParser;
}
public ArrayNode getDatasetNode() {
return targetNode;
}
/*
* JsonObjectReader interface has an empty default method and must be implemented in this case to set
* the mapper and the parser
*/
#Override
public void open(Resource resource) throws Exception {
logger.info("Opening json object reader");
this.inputStream = resource.getInputStream();
JsonNode jsonNode = this.mapper.readTree(this.inputStream).findPath(targetPath);
if (!jsonNode.isMissingNode()) {
this.jsonParser = startArrayParser(jsonNode);
logger.info("Reader open with parser reference: " + this.jsonParser);
this.targetNode = (ArrayNode) jsonNode; // for testing purposes
} else {
logger.severe("Couldn't read target node " + this.targetPath);
throw new UnreachableNodeException();
}
}
#Override
public T read() throws Exception {
try {
if (this.jsonParser.nextToken() == JsonToken.START_OBJECT) {
T result = this.mapper.readValue(this.jsonParser, this.targetType);
logger.info("Object read: " + result.hashCode());
return result;
}
} catch (IOException e) {
throw new ParseException("Unable to read next JSON object", e);
}
return null;
}
/**
* Creates a new parser from an array node
*/
private JsonParser startArrayParser(JsonNode jsonArrayNode) throws IOException {
JsonParser jsonParser = this.mapper.getFactory().createParser(jsonArrayNode.toString());
if (jsonParser.nextToken() == JsonToken.START_ARRAY) {
return jsonParser;
} else {
throw new InvalidArrayNodeException();
}
}
#Override
public void close() throws Exception {
this.inputStream.close();
this.jsonParser.close();
}
}
Java 7, Jersey 2.17 + Jersey-media-moxy 2.17
All JAXB classes have the same namespace.
The FindResponse and ApipeApipe classes are in different package, the package-info class for both packages has the same annotation:
#javax.xml.bind.annotation.XmlSchema(namespace = "xmlapi_1.0", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
The FindResponse class has the #XmlElementRoot,
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
})
#XmlRootElement(name = "findResponse")
public class FindResponse {
while the ApipeApipe class
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "apipe.Apipe")
public class ApipeApipe extends VllVll {
The ObjectFactory for FindResponse:
public FindResponse createFindResponse() {
return new FindResponse();
}
The ObjectFactory for ApipeApipe:
#XmlElementDecl(namespace = "xmlapi_1.0", name = "apipe.Apipe")
public JAXBElement<ApipeApipe> createApipeApipe(ApipeApipe value) {
return new JAXBElement<ApipeApipe>(new QName("xmlapi_1.0", "apipe.Apipe"), ApipeApipe.class, null, value);
}
The custom MoxyJsonConfigResolver:
#Provider
public class MdmMoxyJsonConfigResolver implements ContextResolver<MoxyJsonConfig> {
private final MoxyJsonConfig config;
public MdmMoxyJsonConfigResolver() {
final Map<String, String> namespacePrefixMapper = new HashMap<String, String>();
namespacePrefixMapper.put(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "xsi");
namespacePrefixMapper.put("xmlapi_1.0", "");
config = new MoxyJsonConfig()
.setNamespacePrefixMapper(namespacePrefixMapper)
.setNamespaceSeparator(':')
.setAttributePrefix("#")
.setValueWrapper("value")
.property(JAXBContextProperties.JSON_WRAPPER_AS_ARRAY_NAME, true)
.setFormattedOutput(true)
.setIncludeRoot(true)
.setMarshalEmptyCollections(true);
}
#Override
public MoxyJsonConfig getContext(Class<?> type) {
return config;
}
}
Json output:
{
"findResponse" : {
"result" : {
"ns0:lag.Interface" : [ {
"ns0:objectFullName" : "network:35.121.34.101:lag:interface-31",
"ns0:selfAlarmed" : true,
"ns0:name" : "interface-31",
"ns0:administrativeState" : "portInService",
"ns0:baseMacAddress" : "00-00-00-00-00-00",
"ns0:cleiCode" : "N/A",
"ns0:displayedName" : "Lag 31",
"ns0:equipmentCategory" : "port",
"ns0:equipmentState" : "equipmentOperationallyDown",
"ns0:hardwareFailureReason" : "N/A"}]
}}}
I've set the namespace prefix to blank in the MoxyJsonConfig, it does not seem to work. How can I get rid of the namespace prefix, especially the one prepended to the attributes?
I've added a test to check the output in XML format:
The code:
#Test
public void testMoxyJaxb() throws Exception {
ApipeApipe apipe = findService.findByOfn(ApipeApipe.class, "svc-mgr:service-96849");
FindResponse findResponse = getFindResponse(Arrays.asList(apipe));
{
javax.xml.bind.JAXBContext jaxbContext = javax.xml.bind.JAXBContext.newInstance(getClassesToBeBound());
Marshaller marshaller = jaxbContext.createMarshaller();
System.out.println("javax.xml.bind.JAXBContext output: ");
marshaller.marshal(findResponse, System.out);
System.out.println("");
}
{
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(JAXBContextProperties.DEFAULT_TARGET_NAMESPACE, "xmlapi_1.0");
javax.xml.bind.JAXBContext jaxbContext = org.eclipse.persistence.jaxb.JAXBContextFactory.createContext(getClassesToBeBound(), properties);
org.eclipse.persistence.jaxb.JAXBMarshaller marshaller = (org.eclipse.persistence.jaxb.JAXBMarshaller)jaxbContext.createMarshaller();
Map<String, Object> nsPrefix = new HashMap<String, Object>();
// nsPrefix.put(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "xsi");
nsPrefix.put("xmlapi_1.0", "");
marshaller.setProperty(MarshallerProperties.NAMESPACE_PREFIX_MAPPER, nsPrefix);
System.out.println("org.eclipse.persistence.jaxb.JAXBContextFactory output: ");
marshaller.marshal(findResponse, System.out);
System.out.println("");
}
{
System.out.println("org.springframework.oxm.jaxb.Jaxb2Marshaller output: ");
Result result = new StringResult();
jaxb.marshal(findResponse, result);
System.out.println(result.toString());
System.out.println("");
}
}
The output:
javax.xml.bind.JAXBContext output:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><findResponse xmlns="xmlapi_1.0"><result><apipe.Apipe><objectFullName>svc-mgr:service-96849</objectFullName><selfAlarmed>false</selfAlarmed><name>service-96849</name><groupPointer></groupPointer><id>96849</id><serviceId>500</serviceId><subscriberPointer>subscriber:1</subscriberPointer><administrativeState>down</administrativeState><description>N/A</description><displayedName>APIPE 500</displayedName><aggrOperationalState>down</aggrOperationalState><compositeSvcId>0</compositeSvcId><compositeSvcPointer></compositeSvcPointer><configuredNumberOfSites>0</configuredNumberOfSites><customerName>Default customer</customerName><mtuInconsistent>false</mtuInconsistent><numberOfCircuitsInconsistent>false</numberOfCircuitsInconsistent><numberOfConnector>0</numberOfConnector><numberOfInterfacesInconsistent>false</numberOfInterfacesInconsistent><numberOfSites>0</numberOfSites><numberOfSitesInconsistent>false</numberOfSitesInconsistent><olcState>maintenance</olcState><operationalFlags/><sasEntityName>A-pipe Service #500</sasEntityName><subscriberId>1</subscriberId><vcType>atmvcc</vcType></apipe.Apipe></result></findResponse>
org.eclipse.persistence.jaxb.JAXBContextFactory output:
<?xml version="1.0" encoding="UTF-8"?><findResponse xmlns="xmlapi_1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><result><apipe.Apipe xmlns:ns0="xmlapi_1.0"><objectFullName>svc-mgr:service-96849</objectFullName><selfAlarmed>false</selfAlarmed><name>service-96849</name><groupPointer></groupPointer><id>96849</id><serviceId>500</serviceId><subscriberPointer>subscriber:1</subscriberPointer><administrativeState>down</administrativeState><description>N/A</description><displayedName>APIPE 500</displayedName><aggrOperationalState>down</aggrOperationalState><compositeSvcId>0</compositeSvcId><compositeSvcPointer></compositeSvcPointer><configuredNumberOfSites>0</configuredNumberOfSites><customerName>Default customer</customerName><mtuInconsistent>false</mtuInconsistent><numberOfCircuitsInconsistent>false</numberOfCircuitsInconsistent><numberOfConnector>0</numberOfConnector><numberOfInterfacesInconsistent>false</numberOfInterfacesInconsistent><numberOfSites>0</numberOfSites><numberOfSitesInconsistent>false</numberOfSitesInconsistent><olcState>maintenance</olcState><operationalFlags/><sasEntityName>A-pipe Service #500</sasEntityName><subscriberId>1</subscriberId><vcType>atmvcc</vcType></apipe.Apipe></result></findResponse>
org.springframework.oxm.jaxb.Jaxb2Marshaller output:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><findResponse xmlns="xmlapi_1.0"><result><apipe.Apipe><objectFullName>svc-mgr:service-96849</objectFullName><selfAlarmed>false</selfAlarmed><name>service-96849</name><groupPointer></groupPointer><id>96849</id><serviceId>500</serviceId><subscriberPointer>subscriber:1</subscriberPointer><administrativeState>down</administrativeState><description>N/A</description><displayedName>APIPE 500</displayedName><aggrOperationalState>down</aggrOperationalState><compositeSvcId>0</compositeSvcId><compositeSvcPointer></compositeSvcPointer><configuredNumberOfSites>0</configuredNumberOfSites><customerName>Default customer</customerName><mtuInconsistent>false</mtuInconsistent><numberOfCircuitsInconsistent>false</numberOfCircuitsInconsistent><numberOfConnector>0</numberOfConnector><numberOfInterfacesInconsistent>false</numberOfInterfacesInconsistent><numberOfSites>0</numberOfSites><numberOfSitesInconsistent>false</numberOfSitesInconsistent><olcState>maintenance</olcState><operationalFlags/><sasEntityName>A-pipe Service #500</sasEntityName><subscriberId>1</subscriberId><vcType>atmvcc</vcType></apipe.Apipe></result></findResponse>
There's an additional namespace attribute on tag of the org.eclipse.persistence.jaxb.JAXBMarshaller output. It's not there in the other two marshaller's output. Is this the reason the namespace prefix is prepended to the attribute name of the JSON output?
I have the following json which i need to parse:
{
"resource1": {
"fare": 7511,
"typeoftravel": "domestic",
"vertical": "flight",
"extra": "[{'duration': u'3h 0m', 'arrtime': u'2015-05-05t1930', 'nostops': 0, 'deptime': u'2015-05-05t1630', 'flightno': u'6E478'}]",
"roundtrip": "O",
"destination": "BLR",
"returndate": 0,
"lastupdated": "2015-03-29T10:40:12",
"source": "IXC",
"carrier": "IndiGo",
"date": 20150505,
"class": "E"
},
"resource2": {
"fare": 6320,
"typeoftravel": "domestic",
"vertical": "flight",
"extra": "[{'duration': u'4h 25m', 'arrtime': u'2015-05-06t2210', 'nostops': 0, 'deptime': u'2015-05-06t1745', 'flightno': u'9W7076'}]",
"roundtrip": "O",
"destination": "BLR",
"returndate": 0,
"lastupdated": "2015-03-29T17:08:09",
"source": "IXC",
"carrier": "Jet Airways",
"date": 20150506,
"class": "E"
}
}
I am not able to determine what kind of field "extra" is. It's not a jsonObject or jsonArray. I am able to cast it to jsonPrimitive but now how do i parse this. I am using Gson for parsing.
Assuming you have the following classes:
Resources:
public class Resources {
Resource resource1;
Resource resource2;
}
Resource:
import java.util.List;
public class Resource {
int fare;
String typeoftravel;
String vertical;
String roundtrip;
String destination;
int returndate;
String lastupdated;
String source;
String carrier;
long date;
String clazz;
List<Extra> extra;
}
Extra:
public class Extra {
String duration;
String arrtime;
int nostops;
String deptime;
String flightno;
}
One possible solution, is to register new JsonSerializer to your Gson object, which will
Copy all normal fields, except extra
Extract extra as String
Parse extracted extra again to remove the quotes
Then deserialize it as List using TypeToken
See below code for explanation
ResourceDeserializer
import java.lang.reflect.Type;
import java.util.List;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;
public class ResourceDeserializer implements JsonDeserializer<Resource> {
#Override
public Resource deserialize(JsonElement value, Type type,
JsonDeserializationContext context) throws JsonParseException {
final JsonObject resourceJson = value.getAsJsonObject();
final Resource resource = new Resource();
resource.fare = resourceJson.get("fare").getAsInt();
resource.typeoftravel = resourceJson.get("typeoftravel").getAsString();
resource.vertical = resourceJson.get("vertical").getAsString();
resource.roundtrip = resourceJson.get("roundtrip").getAsString();
resource.destination = resourceJson.get("destination").getAsString();
resource.returndate = resourceJson.get("returndate").getAsInt();
resource.lastupdated = resourceJson.get("lastupdated").getAsString();
resource.source = resourceJson.get("source").getAsString();
resource.carrier = resourceJson.get("carrier").getAsString();
resource.date = resourceJson.get("date").getAsLong();
resource.clazz = resourceJson.get("class").getAsString();
Type listType = new TypeToken<List<Extra>>(){}.getType();
String extraStr = resourceJson.get("extra").getAsString();
extraStr = extraStr.replaceAll("u?(\'[^\']+\')", "$1");
JsonElement extraList = new JsonParser().parse(extraStr);
resource.extra = context.deserialize(extraList, listType);
return resource;
}
}
Registering the above deserializer
Gson gson = new GsonBuilder()
.registerTypeAdapter(Resource.class, new ResourceDeserializer())
.create();
Note that, I renamed the attribute class to clazz, because class
is a reserved word!
I am planning to serialize list of JAXB objects to JSON response. Currently below is the format I am getting. In the below response I am seeing one more object in between is "systemInfoList" which actually is showing the array. Instead I want the dependent_systems_infos should directly show array []. Also if there is a single system info response also still it should should show in the array format. I am using the Jackson parser, cxf.
Format currently I am getting:
{
"dependent_systems_infos":{
"systemInfoList":[
{
"system_name":"PZ_Service",
"system_type":"Internal",
"service_infos":[
{
"service_name":"getPZAttributes",
"status":"DOWN",
"response_time_ms":50
}
]
},
{
"system_name":"OMS",
"system_type":"External",
"service_infos":[
{
"service_name":"CreateOrder",
"status":"UP",
"response_time_ms":2000
},
{
"service_name":"CancelOrder",
"status":"UP",
"response_time_ms":500
}
]
}
]
}
}
Format I need:
{
dependent_system_infos : [
{
system_name : 'OMS'
system_type: 'External'
services_infos: [
{
service_name : 'CreateOrder'
status : 'UP'
response_time_ms : 2000
},
{
service_name : 'CancelOrder'
status : 'UP'
response_time_ms : 2000
}
]
},
{
system_name : 'PZ_Service'
system_type: 'Internal'
services_infos: [
{
service_name : 'getPZAttributes'
status : 'UP'
response_time_ms : 2000
}
]
}
]
}
JAXB classes I wrote:
#XmlRootElement(name = "dependent_systems_infos")
#XmlAccessorType(XmlAccessType.FIELD)
public class ItineraryStatusResponse {
private List<SystemInfo> systemInfoList;
#XmlList
public List<SystemInfo> getSystemInfoList() {
return systemInfoList;
}
public void setSystemInfoList(List<SystemInfo> systemInfoList) {
this.systemInfoList = systemInfoList;
}
}
#XmlType(propOrder = {
"systemName",
"systemType",
"serviceInfoList"
})
#XmlAccessorType(XmlAccessType.FIELD)
public class SystemInfo {
#XmlElement(name = "system_name", required = true)
protected SystemName systemName;
#XmlElement(name = "system_type", required = true)
protected SystemType systemType;
#XmlElement(name = "service_infos", required = true)
protected List<ServiceInfo> serviceInfoList;
}
It would help to know how you're generating the output, but the main issue is that you are serializing a root object that contains a list when you really only want to serialize the list itself. What would you expect the outputted list to look like if ItineraryStatusResponse had other fields in it?
You can remove the #XmlRootElement annotation and mark the list as an element named "dependent_systems_infos":
#XmlAccessorType(XmlAccessType.FIELD)
public static class ItineraryStatusResponse {
private List<SystemInfo> systemInfoList;
#XmlElement(name = "dependent_systems_infos", required = true)
public List<SystemInfo> getSystemInfoList() {
return systemInfoList;
}
public void setSystemInfoList(List<SystemInfo> systemInfoList) {
this.systemInfoList = systemInfoList;
}
}
If you are doing the serialization yourself, another approach would be to drop the ItineraryStatusResponse object entirely (since it's just a wrapper around the list), and then serialize the list itself with SerializationFeature.WRAP_ROOT_VALUE = true and a root name you provide:
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
AnnotationIntrospector introspector = new JaxbAnnotationIntrospector(TypeFactory.defaultInstance());
mapper.setAnnotationIntrospector(introspector);
ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter().withRootName("dependent_systems_infos");
System.out.println(writer.writeValueAsString(systemInfoList));
Both of these approaches provide the desired output in my testing with Jackson 2.2.