Issues while deserializing exception/throwable using Jackson in Java - java

I am facing issues while deserializing Exception and Throwable instances using Jackson (version 2.2.1). Consider the following snippet:
public static void main(String[] args) throws IOException
{
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);
objectMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
objectMapper.enableDefaultTyping(DefaultTyping.NON_FINAL, As.PROPERTY);
try {
Integer.parseInt("String");
}
catch (NumberFormatException e) {
RuntimeException runtimeException = new RuntimeException(e);
String serializedException = objectMapper.writeValueAsString(runtimeException);
System.out.println(serializedException);
Throwable throwable = objectMapper.readValue(serializedException, Throwable.class);
throwable.printStackTrace();
}
}
The output of System.out.println in the catch block is:
{
"#class" : "java.lang.RuntimeException",
"detailMessage" : "java.lang.NumberFormatException: For input string: \"String\"",
"cause" : {
"#class" : "java.lang.NumberFormatException",
"detailMessage" : "For input string: \"String\"",
"cause" : null,
"stackTrace" : [ {
"declaringClass" : "java.lang.NumberFormatException",
"methodName" : "forInputString",
"fileName" : "NumberFormatException.java",
"lineNumber" : 65
}, {
"declaringClass" : "java.lang.Integer",
"methodName" : "parseInt",
"fileName" : "Integer.java",
"lineNumber" : 492
}, {
"declaringClass" : "java.lang.Integer",
"methodName" : "parseInt",
"fileName" : "Integer.java",
"lineNumber" : 527
}, {
"declaringClass" : "test.jackson.JacksonTest",
"methodName" : "main",
"fileName" : "JacksonTest.java",
"lineNumber" : 26
} ],
"suppressedExceptions" : [ "java.util.ArrayList", [ ] ]
},
"stackTrace" : [ {
"declaringClass" : "test.jackson.JacksonTest",
"methodName" : "main",
"fileName" : "JacksonTest.java",
"lineNumber" : 29
} ],
"suppressedExceptions" : [ "java.util.ArrayList", [ ] ]
}
which seems fine. But when I attempt to deserialize this using objectMapper.readValue(), I get the following exception:
Exception in thread "main" com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "declaringClass" (class java.lang.StackTraceElement), not marked as ignorable
at [Source: java.io.StringReader#3c5ebd39; line: 9, column: 27] (through reference chain: java.lang.StackTraceElement["declaringClass"])
at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:79)
at com.fasterxml.jackson.databind.DeserializationContext.reportUnknownProperty(DeserializationContext.java:555)
at com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:708)
at com.fasterxml.jackson.databind.deser.std.JdkDeserializers$StackTraceElementDeserializer.deserialize(JdkDeserializers.java:414)
at com.fasterxml.jackson.databind.deser.std.JdkDeserializers$StackTraceElementDeserializer.deserialize(JdkDeserializers.java:380)
at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:151)
...
I then tried using mix-in annotations, to ignore declaringClass in java.lang.StackTraceElement, but now the deserialized Exception doesn't contain the declaring class in its stack trace:
java.lang.RuntimeException: java.lang.NumberFormatException: For input string: "String"
at .main(JacksonTest.java:33)
Caused by: java.lang.NumberFormatException: For input string: "String"
at .forInputString(NumberFormatException.java:65)
at .parseInt(Integer.java:492)
at .parseInt(Integer.java:527)
at .main(JacksonTest.java:30)
Am I missing anything? Any help is greatly appreciated.

There seems to be a Jackson JIRA entry for this here. Jackson doesn't seem to be able to handle the declaringClass in java.lang.StackTraceElement, since the getter corresponding to this field is called getClassName().
I fixed this issue by using a custom wrapper around StackTraceElement as suggested in the JIRA entry mentioned above. The custom wrapper (CustomStackTraceElement) will have the fields declaringClass, methodName, fileName, and lineNumber and the corresponding getters and setters in it. I modified the catch block (mentioned in the question) to be as follows:
catch (NumberFormatException e) {
RuntimeException runtimeException = new RuntimeException(e);
e.printStackTrace();
String serializedException = objectMapper.writeValueAsString(runtimeException);
System.out.println(serializedException);
String serializedStackTrace = objectMapper.writeValueAsString(transformStackTrace(runtimeException));
String serializedStackTraceForCause = objectMapper.writeValueAsString(transformStackTrace(runtimeException.getCause()));
Throwable throwable = objectMapper.readValue(serializedException, Throwable.class);
List<CustomStackTraceElement> customStackTraceElementList = objectMapper.readValue(serializedStackTrace, List.class);
List<CustomStackTraceElement> customStackTraceElementListForCause = objectMapper.readValue(serializedStackTraceForCause, List.class);
throwable.setStackTrace(reverseTransformStackTrace(customStackTraceElementList));
throwable.getCause().setStackTrace(reverseTransformStackTrace(customStackTraceElementListForCause));
throwable.printStackTrace();
}
The StackTraceElement[] will be converted into List<CustomStackTraceElement> by the following method during serialization:
private static List<CustomStackTraceElement> transformStackTrace(Throwable throwable)
{
List<CustomStackTraceElement> list = new ArrayList<>();
for (StackTraceElement stackTraceElement : throwable.getStackTrace()) {
CustomStackTraceElement customStackTraceElement =
new CustomStackTraceElement(stackTraceElement.getClassName(),
stackTraceElement.getMethodName(),
stackTraceElement.getFileName(),
stackTraceElement.getLineNumber());
list.add(customStackTraceElement);
}
return list;
}
... and the reverse transformation will be done during deserialization:
private static StackTraceElement[] reverseTransformStackTrace(List<CustomStackTraceElement> customStackTraceElementList)
{
StackTraceElement[] stackTraceElementArray = new StackTraceElement[customStackTraceElementList.size()];
for (int i = 0; i < customStackTraceElementList.size(); i++) {
CustomStackTraceElement customStackTraceElement = customStackTraceElementList.get(i);
StackTraceElement stackTraceElement =
new StackTraceElement(customStackTraceElement.getDeclaringClass(),
customStackTraceElement.getMethodName(),
customStackTraceElement.getFileName(),
customStackTraceElement.getLineNumber());
stackTraceElementArray[i] = stackTraceElement;
}
return stackTraceElementArray;
}
Now, after deserialization, the Throwable object has the expected stack trace in it.

Add this:
objectMapper.configure( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
And make out of the deserialized exception the same way, as for the first time:
System.out.println( objectMapper.writeValueAsString( throwable ) );
I used the following code:
public static void main( String[] args ) throws IOException
{
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure( SerializationFeature.INDENT_OUTPUT, true );
objectMapper.configure( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setVisibility( PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY );
objectMapper.enableDefaultTyping( ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY );
try
{
Integer.parseInt( "String" );
}
catch( NumberFormatException e )
{
Throwable throwable = objectMapper.readValue( objectMapper.writeValueAsString( e ), Throwable.class );
System.out.println( objectMapper.writeValueAsString( throwable ) );
}
}
Added this jars: jackson-annotations-2.2.0.jar, jackson-core-2.2.0.jar and jackson-databind-2.2.0.jar.
After execution, the following is printed:
{
"#class" : "java.lang.NumberFormatException",
"detailMessage" : "For input string: \"String\"",
"cause" : null,
"stackTrace" : [ {
"declaringClass" : "java.lang.NumberFormatException",
"methodName" : "forInputString",
"fileName" : "NumberFormatException.java",
"lineNumber" : 48,
"className" : "java.lang.NumberFormatException",
"nativeMethod" : false
}, {
"declaringClass" : "java.lang.Integer",
"methodName" : "parseInt",
"fileName" : "Integer.java",
"lineNumber" : 449,
"className" : "java.lang.Integer",
"nativeMethod" : false
}, {
"declaringClass" : "java.lang.Integer",
"methodName" : "parseInt",
"fileName" : "Integer.java",
"lineNumber" : 499,
"className" : "java.lang.Integer",
"nativeMethod" : false
}, {
"declaringClass" : "com.sample.bla.Main",
"methodName" : "main",
"fileName" : "Main.java",
"lineNumber" : 24,
"className" : "com.sample.bla.Main",
"nativeMethod" : false
}, {
"declaringClass" : "sun.reflect.NativeMethodAccessorImpl",
"methodName" : "invoke0",
"fileName" : "NativeMethodAccessorImpl.java",
"lineNumber" : -2,
"className" : "sun.reflect.NativeMethodAccessorImpl",
"nativeMethod" : true
}, {
"declaringClass" : "sun.reflect.NativeMethodAccessorImpl",
"methodName" : "invoke",
"fileName" : "NativeMethodAccessorImpl.java",
"lineNumber" : 39,
"className" : "sun.reflect.NativeMethodAccessorImpl",
"nativeMethod" : false
}, {
"declaringClass" : "sun.reflect.DelegatingMethodAccessorImpl",
"methodName" : "invoke",
"fileName" : "DelegatingMethodAccessorImpl.java",
"lineNumber" : 25,
"className" : "sun.reflect.DelegatingMethodAccessorImpl",
"nativeMethod" : false
}, {
"declaringClass" : "java.lang.reflect.Method",
"methodName" : "invoke",
"fileName" : "Method.java",
"lineNumber" : 597,
"className" : "java.lang.reflect.Method",
"nativeMethod" : false
}, {
"declaringClass" : "com.intellij.rt.execution.application.AppMain",
"methodName" : "main",
"fileName" : "AppMain.java",
"lineNumber" : 120,
"className" : "com.intellij.rt.execution.application.AppMain",
"nativeMethod" : false
} ],
"message" : "For input string: \"String\"",
"localizedMessage" : "For input string: \"String\""
}

It seems that the output you get in version 2.2.1 is not the same as I get with version 2.2.0 (which according to the website is the latest 2.x version). Besides the latest available 2.x version on the Maven Repository is 2.2.2. So I would try to either downgrade it to 2.2.0 or to upgrade it to 2.2.2. If any of the changes brings you the expected result, I would go further with that version and open a BUG in Jackson's JIRA.
And of course don't forget
objectMapper.configure( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
from Michael's answer.

Try using polymorphism so that jackson deserializer knows what kind of Throwable to create:
/**
* Jackson module to serialize / deserialize Throwable
*/
public class ThrowableModule extends SimpleModule {
public ThrowableModule() {
super("Throwable", new Version(1, 0, 0, null, null, null));
}
#Override
public void setupModule(SetupContext context) {
context.setMixInAnnotations(Throwable.class, ThrowableAnnotations.class);
}
/**
* Add annotation to Throwable so that the class name is serialized with the instance data.
*/
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "class")
static abstract class ThrowableAnnotations {
}
}

I've had a similar issue. I'm using this code now, and it allows me to serialize and deserialize exceptions with proper types (i.e. a RuntimeException will be a RuntimeException again :)):
public static ObjectMapper createObjectMapper() {
ObjectMapper mapper = new ObjectMapper(null, null, new DefaultDeserializationContext.Impl(
new BeanDeserializerFactory(new DeserializerFactoryConfig()) {
private static final long serialVersionUID = 1L;
#Override
public JsonDeserializer<Object> buildThrowableDeserializer(
DeserializationContext ctxt, JavaType type, BeanDescription beanDesc)
throws JsonMappingException {
return super.buildBeanDeserializer(ctxt, type, beanDesc);
}
}));
mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
mapper.addMixIn(Throwable.class, ThrowableMixin.class);
mapper.addMixIn(StackTraceElement.class, StackTraceElementMixin.class);
return mapper;
}
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "#class")
#JsonAutoDetect(fieldVisibility = Visibility.ANY)
#JsonIgnoreProperties({ "message", "localizedMessage", "suppressed" })
abstract class ThrowableMixin {
#JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "$id")
private Throwable cause;
}
abstract class StackTraceElementMixin {
#JsonProperty("className")
private String declaringClass;
}
I'm manipulating the BeanDeserializerFactory to make buildThrowableDeserializer not treat Throwable any special but just like any other Object. Then using Mixins to define the "special" handling of Throwable and StackTraceElement to my liking.

Is it so necessary to use json serialization? Looks liks there are some bugs with throwables. Why not use system api:
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream( );
ObjectOutputStream objectOutputStream = new ObjectOutputStream( byteArrayOutputStream );
objectOutputStream.writeObject( e );
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream( byteArrayOutputStream.toByteArray() );
ObjectInputStream objectInputStream = new ObjectInputStream( byteArrayInputStream );
Throwable t = (Throwable) objectInputStream.readObject();

Related

Java PUT Request Zotero 400 Bad Request

I am trying to send PUT request to the Zotero API, but I keep getting an error:
Caused by: org.springframework.web.client.HttpClientErrorException$BadRequest: 400 Bad Request: ['itemType' property not provided]
The JSON being sent is fine, so it is something with my code.
private void handleUpdateItemButton(ActionEvent event) throws IOException {
Properties props = restConnection.getAccessProperties();
ResponseEntity<JsonNode> res = restConnection.getRestTemplate().exchange(this.getItem(props, itemKey), new ParameterizedTypeReference<JsonNode>() {
});
if (res.getStatusCode() == HttpStatus.OK) {
JsonNode jsonNode = res.getBody();
printJSON(jsonNode);
JSONObject jsonObject = convertNodetoObject(jsonNode);
JSONObject jsonData = jsonObject.getJSONObject("data");
//jsonObject.getJSONObject("data").put("title", "This is the new title");
jsonData.put("title", "This is the new title");
ResponseEntity<JsonNode> updatedItem = restConnection.getRestTemplate().exchange(this.updateItem(props, jsonData, itemKey), new ParameterizedTypeReference<JsonNode>() {
});
}
else{
System.out.println("This item cannot be updated");
}
}
The method above then calls the method below
private RequestEntity updateItem(Properties props, JSONObject item, String itemKey) throws JsonProcessingException {
ResponseEntity<JsonNode> res = restConnection.getRestTemplate().exchange(this.getItem(props, itemKey), new ParameterizedTypeReference<JsonNode>() {
});
return RequestEntity
.put(restConnection.getZoteroBaseURL() + "/users/" + props.getProperty("username") + "/items/" + itemKey)
.header("Zotero-API-Version", "3")
.header("Zotero-API-Key", props.getProperty("key"))
.header("If-Unmodified-Since-Version", numberBody.get("version").toString())
.header("Content-Type", "application/json")
.body(item);
}
Not really sure what is wrong. I'd appreciate any help - zoter-dev said that the PUT request should work and it's something with my code. Thanks!
I'd suggest you take a good look at the Zotero Web API documentation.
If you examine the creating an item section you'll find what you need to pass in your API call in order for it to work:
[
{
"itemType" : "book",
"title" : "My Book",
"creators" : [
{
"creatorType":"author",
"firstName" : "Sam",
"lastName" : "McAuthor"
},
{
"creatorType":"editor",
"name" : "John T. Singlefield"
}
],
"tags" : [
{ "tag" : "awesome" },
{ "tag" : "rad", "type" : 1 }
],
"collections" : [
"BCDE3456", "CDEF4567"
],
"relations" : {
"owl:sameAs" : "http://zotero.org/groups/1/items/JKLM6543",
"dc:relation" : "http://zotero.org/groups/1/items/PQRS6789",
"dc:replaces" : "http://zotero.org/users/1/items/BCDE5432"
}
}
]
It's stated that All properties other than itemType, tags, collections, and relations are optional, meaning itemType is mandatory.
You must fill in these four properties, at least, if you want your call to succeed.
If you don't have any data for tags, collections or relations you could just pass empty property values:
{
"itemType" : "note",
"note" : "My sample note",
"tags" : [],
"collections" : [],
"relations" : {}
}

Docx4j functionality to turn a document into JSON representation?

Is there a good way to convert a document into JSON representation to then display on a web page? (It is a requirement that the document is converted to JSON)
My Idea if there isn't a built in way to do this is to represent the Run/Paragraph structure as JSON Objects, but I feel like this wouldn't work as well once I start working with more complex Word Documents.
If you add:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.11.3</version>
</dependency>
you can try something like:
import org.docx4j.Docx4J;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
public class ConvertOutJSON {
static String inputfilepath = System.getProperty("user.dir") + "/sample-docs/sample-docxv2.docx";
public static void main(String[] args)
throws Exception {
WordprocessingMLPackage wordMLPackage
= Docx4J.load(new java.io.File(inputfilepath));
String xml = wordMLPackage.getMainDocumentPart().getXML();
//System.out.println(xml);
XmlMapper xmlMapper = new XmlMapper();
JsonNode node = xmlMapper.readTree(xml);
ObjectMapper jsonMapper = new ObjectMapper();
//String json = jsonMapper.writeValueAsString(node);
String json = jsonMapper.writerWithDefaultPrettyPrinter().writeValueAsString(node);
System.out.println(json);
}
}
However in a quick test, I noticed some w:p nodes were not being emitted as JSON. I haven't looked to see whether they get dropped by Jackson at the readTree step or when ObjectMapper writes its output; you'll need to dig into Jackson to fix that.
It is currently producing output like:
{
"Ignorable" : "w14 wp14",
"body" : {
"p" : {
"rsidR" : "00D15781",
"rsidRDefault" : "00D15781",
"pPr" : {
"ind" : {
"left" : "0"
}
}
},
"tbl" : {
"tblPr" : {
"tblStyle" : {
"val" : "TableGrid"
},
"tblW" : {
"w" : "0",
"type" : "auto"
},
"tblLook" : {
"firstRow" : "1",
"lastRow" : "0",
"firstColumn" : "1",
"lastColumn" : "0",
"noHBand" : "0",
"noVBand" : "1",
"val" : "04A0"
}
},
"tblGrid" : {
"gridCol" : {
"w" : "3561"
}
},
"tr" : {
"rsidR" : "00D15781",
"tc" : {
"tcPr" : {
"tcW" : {
"w" : "7122",
"type" : "dxa"
},
"gridSpan" : {
"val" : "2"
}
},
"p" : {
"rsidR" : "00D15781",
"rsidRDefault" : "00945132",
"pPr" : {
"ind" : {
"left" : "0"
}
},
"r" : {
"t" : "Horizontal merge"
}
}
}
}
},
"sectPr" : {
"rsidR" : "00D15781",
"headerReference" : {
"type" : "default",
"id" : "rId12"
},
"pgSz" : {
"w" : "11907",
"h" : "16839",
"code" : "9"
},
"pgMar" : {
"top" : "720",
"right" : "720",
"bottom" : "720",
"left" : "720",
"header" : "720",
"footer" : "720",
"gutter" : "0"
},
"cols" : {
"space" : "720"
},
"docGrid" : {
"linePitch" : "360"
}
}
}
}

Mapping JSON String to List of POJO gives null values

I'm getting json from rest api and I want to store the data in list of POJO. Below is the codefor the same:
public List<myObject> mapper(){
String myObjectData= restClient.getAllOriginal("myObject");
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
CollectionType typeReference =
TypeFactory.defaultInstance().constructCollectionType(List.class, myObject.class);
List<CommitmentPojo> resultDto = null;
try
{
resultDto = objectMapper.readValue(myObjectData, typeReference);
}
catch (JsonParseException e)
{
e.printStackTrace();
}
catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return resultDto;
}
I've added FAIL_ON_UNKNOWN_PROPERTIES configuration as I've extra columns in json as compared to POJO and I can't change POJO(unless and until required) as I'll have to change many more things. I've added ACCEPT_SINGLE_VALUE_AS_ARRAY configuration for object mapper as I was facing exception in below line: (I suspect this is causing the issue now)
// [JACKSON-526]: implicit arrays from single values?
if (!ctxt.isEnabled(DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)) {
throw ctxt.mappingException(_collectionType.getRawClass());
}
This is from CollectionDeserializer.handleNonArray method.
Method which gets the string from rest api:
public String getAllOriginal(String resourcePath) {
// Objects.requireNonNull(this.baseUri, "target cannot be null");
return this.client
.target("http://comtsrvc.ny.qa.flx.nimbus.gs.com:3802/v2/")
.path(resourcePath)
.request(MediaType.APPLICATION_JSON_TYPE)
.cookie("GSSSO", getCookie())
.get()
.readEntity(String.class);
}
Below is my json:
{
"myObject" : [ {
"key" : {
"srcSys" : "REPO_1",
"srcSysRef" : "20200909_1911_1"
},
"productData" : {
"id" : null,
"number" : null,
"isn" : null,
"productId" : null,
"productAdditionalData" : {
"assetClassTree" : "UNCLASSIFIED",
"description" : "UNCLASSIFIED",
"productTypeData" : {
"productType" : "UNCLASSIFIED",
"productGroup" : "UNCLASSIFIED"
}
}
},
"state" : "OPEN",
"type" : "01"
}, {
"key" : {
"srcSys" : "REPO_2",
"srcSysRef" : "20200403_3892_1"
},
"productData" : {
"id" : "1",
"number" : "11",
"isn" : "null",
"productId" : 1234,
"productAdditionalData" : {
"assetClassTree" : "xyz",
"description" : "abc",
"productTypeData" : {
"productType" : "UNCLASSIFIED",
"productGroup" : "UNCLASSIFIED"
}
}
},
"state" : "OPEN",
"tradAcctType" : "01"
} ]
}
The issue is: all the values are null with the size of list as 1. Can you please tell me what is wrong with my code.
Try to deserialize it to a Map:
import com.fasterxml.jackson.core.type.TypeReference;
...
Map<String, List<MyObject>> root = mapper.readValue(jsonFile, new TypeReference<Map<String, List<MyObject>>>() {});
List<MyObject> objects = root.get("myObject");
So you do not need to create a new POJO for a root level. Map will also work.

JSON Path: how to convert URN references to local references

Whilst converting a JSON schema file to Java classes using the Maven org.jsonschema2pojo:jsonschema2pojo-maven-plugin:1.0.0-alpha2 I am getting errors related to urn references which cannot be resolved.
Here is a sample error message:
[ERROR] Failed to execute goal org.jsonschema2pojo:jsonschema2pojo-maven-plugin:1.0.0-alpha2:generate (default) on project model-reservation: Execution default of goal org.jsonschema2pojo:jsonschema
2pojo-maven-plugin:1.0.0-alpha2:generate failed: Unrecognised URI, can't resolve this: urn:jsonschema:com:lumina:pnr:model:Reference: unknown protocol: urn -> [Help 1]
Here is the JSON it refers to with the $ref element which is causing the exception:
"remarkFields": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "object",
"id": "urn:jsonschema:com:lumina:pnr:model:FileFinishingField",
"properties": {
"lineNumber": {
"type": "integer"
},
"name": {
"type": "string"
},
"value": {
"type": "string"
},
"references": {
"type": "array",
"items": {
"type": "object",
"$ref": "urn:jsonschema:com:lumina:pnr:model:Reference"
}
}
}
}
}
}
How can I convert these to local references in Java and use the conversion output to successfully convert my code to Java classes using the org.jsonschema2pojo:jsonschema2pojo-maven-plugin:1.0.0-alpha2 Maven plugin?
There might be better ways to solve this problem, yet one possible solution is this one:
Use a recursive method to convert the urn style references to local references:
Here is the recursive method using the Jackson library:
private static final String ITEMS = "items";
private static final String ID = "id";
private static final String PROPERTIES = "properties";
private static final String ADDITIONAL_PROPERTIES = "additionalProperties";
private static final String REF = "$ref";
private static void parseReferences(JsonNode jsonNode, String path) {
if (jsonNode.has(ID)) {
typeMap.put(jsonNode.get(ID).asText(), path);
final JsonNode properties = jsonNode.get(PROPERTIES);
final Iterator<Map.Entry<String, JsonNode>> fields = properties.fields();
path += "/" + PROPERTIES;
while (fields.hasNext()) {
Map.Entry<String, JsonNode> entry = fields.next();
parseReferences(entry.getValue(), path + "/" + entry.getKey());
}
} else if (jsonNode.has(ITEMS)) {
final JsonNode item = jsonNode.get(ITEMS);
parseReferences(item, path + "/" + ITEMS);
} else if (jsonNode.has(REF)) {
ObjectNode objectNode = (ObjectNode) jsonNode;
objectNode.set(REF, new TextNode(typeMap.get(jsonNode.get(REF).asText())));
} else if (jsonNode.has(ADDITIONAL_PROPERTIES)) {
JsonNode additionalProperties = jsonNode.get(ADDITIONAL_PROPERTIES);
parseReferences(additionalProperties, path + "/" + ADDITIONAL_PROPERTIES);
}
}
This is how I am calling this method after having instantiated a JAXB parser:
private static void writeSchemaToFile(ObjectMapper jaxbObjectMapper, String origPath, String path) throws Exception {
InputStream resourceAsStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(origPath);
try (Reader r = new InputStreamReader(resourceAsStream, "UTF-8")) {
JsonNode root = jaxbObjectMapper.readTree(r);
parseReferences(root, "#");
String changedJson = jaxbObjectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(root);
final Path targetPath = Paths.get(path);
if (!Files.exists(targetPath)) {
Path parent = targetPath.getParent();
if (parent != null) {
Files.createDirectories(parent);
}
}
try (Writer writer = Files.newBufferedWriter(targetPath, Charset.forName("UTF-8"), StandardOpenOption.CREATE)) {
writer.write(changedJson);
}
}
}
If you call this method, it will convert the JSON specified in the question to:
"remarkFields" : {
"type" : "object",
"additionalProperties" : {
"type" : "array",
"items" : {
"type" : "object",
"id" : "urn:jsonschema:com:lumina:pnr:model:FileFinishingField",
"properties" : {
"lineNumber" : {
"type" : "integer"
},
"name" : {
"type" : "string"
},
"value" : {
"type" : "string"
},
"references" : {
"type" : "array",
"items" : {
"type" : "object",
"$ref" : "#/properties/passengers/items/properties/reference"
}
}
}
}
}
}

map json nested object from mongodb to java

I'm getting json object on my mongodb with virustotal API
This is how a json object stored in mongodb object looks like :
{
"_id" : ObjectId("597cd2f871eac714388b2f7f"),
"results" : {
"scans" : {
"Bkav" : {
"detected" : true,
"version" : "1.3.0.8042",
"result" : "W32.HfsAutoB.971A",
"update" : "20160706"
},
"TotalDefense" : {
"detected" : false,
"version" : "37.1.62.1",
"result" : null,
"update" : "20160706"
},
"MicroWorld-eScan" : {
"detected" : true,
"version" : "12.0.250.0",
"result" : "Packer.Expressor.B",
"update" : "20160706"
},
"nProtect" : {
"detected" : true,
"version" : "2016-07-06.01",
"result" : "Packer.Expressor.B",
"update" : "20160706"
},
"ALYac" : {
"detected" : false,
"version" : "1.0.1.9",
"result" : null,
"update" : "20160706"
},
"TrendMicro" : {
"detected" : true,
"version" : "9.740.0.1012",
"result" : "TROJ_GEN.R047C0CAP16",
"update" : "20160706"
},
"McAfee-GW-Edition" : {
"detected" : true,
"version" : "v2015",
"result" : "BehavesLike.Win32.Flyagent.cc",
"update" : "20160706"
},
"Sophos" : {
"detected" : true,
"version" : "4.98.0",
"result" : "W32/Pidgeon-A",
"update" : "20160706"
},
"Cyren" : {
"detected" : true,
"version" : "5.4.16.7",
"result" : "W32/SysVenFak.A.gen!Eldorado",
"update" : "20160706"
},
"Microsoft" : {
"detected" : true,
"version" : "1.1.12902.0",
"result" : "Backdoor:Win32/Delf.SJ",
"update" : "20160706"
},
"AegisLab" : {
"detected" : true,
"version" : "4.2",
"result" : "Backdoor.W32.BlackHole.acx!c",
"update" : "20160706"
},
"Qihoo-360" : {
"detected" : false,
"version" : "1.0.0.1120",
"result" : null,
"update" : "20160706"
}
},
"scan_id" : "2ad6e0aad0b40f152f234787daa4afb87538f3278f5c8f815d53ef46d5eea4ac-1467833095",
"sha1" : "c5dcd5526ac5330ad1e9fad51488718329fdb697",
"resource" : "0a60424e0967b6cfc172dac82e10a2fe",
"response_code" : 1,
"scan_date" : "2016-07-06 19:24:55",
"permalink" : "https://www.virustotal.com/file/2ad6e0aad0b40f152f234787daa4afb87538f3278f5c8f815d53ef46d5eea4ac/analysis/1467833095/",
"verbose_msg" : "Scan finished, information embedded",
"total" : 54,
"positives" : 41,
"sha256" : "2ad6e0aad0b40f152f234787daa4afb87538f3278f5c8f815d53ef46d5eea4ac",
"md5" : "0a60424e0967b6cfc172dac82e10a2fe"
},
"response_code" : 200
}
As you can see the json object is too complicated to just get given value from,
This is what i've tried so far :
MongoClient mongo = new MongoClient("localhost", 27017);
MongoDatabase database1 = mongo.getDatabase(db);
MongoCollection<Document> collection1 = database1.getCollection(col);
try (MongoCursor<Document> cursor = collection1.find().iterator()){
while (cursor.hasNext()){
Document doc = cursor.next();
List list = new ArrayList(doc.values());
System.out.println(list.get(1));
}
}
I was thinking maybe there is a way to map all this json to a java class, the main problem is with the "scans" as there are many different scannors and it isn't optimized to create a java class model to each of them,
My question is how can i store directly my json objects to a java object so as to operate on the results returned.
I am calling your main model as Scan. You can create a POJO (lets call it Scanner) with below attributes:
scannerName, detected, version, result, update;
Scanner.java
private String scannerName;
private String detected;
private String version;
private String result;
private String update;
Scan.java
private String scan_id;
private List<Scanner> = new ArrayList<Scanner>();
private String sha1;
private String resource;
......
........
So your scan model now has a List of scanners.
Using morphia its something like:
#Embedded
private List<Scanner> scanner;
If you are not using any wrapper around the java-driver, just try
private List<BasicDBObject>
You need to create two java classes in order to parse Json of coming from Mongo,Suppose First Class Name "CollectionReceived"
Second Class Name "Result"
In CollectionReceived class you need to declare members
public String _id;
public String response_code;
public Result result;
In Result class you need to declare members
Map<String,Map<String,Map<String,Map<String,Object>>>> results=new HashMap<String,Map<String,Map<String,Map<String,Object>>>>();,
public String scan_id;
public String sha1;
public String resource;
public String response_code;
public String scan_date;
public String permalink;
public String verbose_msg;
public String total;
public String positives;
public String sha256;
public String md5;
and don't forget to use #RestController on your controller, You will parse Json Easily and get all the values you required.

Categories