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.
Related
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" : {}
}
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"
}
}
}
}
I want to remove the Null and Empty values from my JSON Response.
Below is Json String:
{"implDisplayablePricePlan": [
{
"productSpecPricing": {
"childPricingSchema": {}
},
"assignedPricePlanID": "abc",
"name": "GOLD",
"action": "Something",
"status": "Something",
"selected": true,
"crossProductDiscount": false,
"displayInformation": {
"visible": true,
"enabled": false
}
}]
}
in this case : productSpecPricing with childPricingSchema
What will be best approach to remove empty objects which will be generic for any JSON String.
Using Jackson you can easy set your preferences to serialize your objects
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
objectMapper.setSerializationInclusion(Include.NON_NULL);
objectMapper.setSerializationInclusion(Include.NON_EMPTY);
ObjectWriter writer = objectMapper.writer();
System.out.println(writer.writeValueAsString(YOUR_OBJECT));
I got this:
{
"implDisplayablePricePlan" : [ {
"productSpecPricing" : { },
"assignedPricePlanID" : "abc",
"name" : "GOLD",
"action" : "Something",
"status" : "Something",
"selected" : true,
"crossProductDiscount" : false,
"displayInformation" : {
"visible" : true,
"enalble" : false
}
} ]
}
as productSpecPricing isn't null (or empty array/collection) it's shown, to solve it you have to add a filter:
annotate the class that contains the productSpecPricing property with #JsonFilter("myFilter")
FilterProvider filters = new SimpleFilterProvider().addFilter("myFilter", filter);
ObjectWriter writer = objectMapper.writer(filters);
Filter:
PropertyFilter filter = new SimpleBeanPropertyFilter() {
#Override
public void serializeAsField(Object pojo, JsonGenerator jgen,
SerializerProvider provider, PropertyWriter writer)
throws Exception {
if (include(writer)) {
System.out.println(writer.getName());
if (!writer.getName().equals("productSpecPricing")) {
writer.serializeAsField(pojo, jgen, provider);
return;
} else {
ProductSpecPricing productSpecPricing = ((YOU_OBJECT) pojo).getProductSpecPricing();
if (productSpecPricing != null && productSpecPricing.getChildPricingSchema() != null && !productSpecPricing.getChildPricingSchema().isEmpty()) {
writer.serializeAsField(pojo, jgen, provider);
}
}
} else if (!jgen.canOmitFields()) {
writer.serializeAsOmittedField(pojo, jgen, provider);
}
}
#Override
protected boolean include(PropertyWriter writer) {
return true;
}
#Override
protected boolean include(BeanPropertyWriter writer) {
return true;
}
};
after apply the filter the result is:
{
"implDisplayablePricePlan" : [ {
"assignedPricePlanID" : "abc",
"name" : "GOLD",
"action" : "Something",
"status" : "Something",
"selected" : true,
"crossProductDiscount" : false,
"displayInformation" : {
"visible" : true,
"enalble" : false
}
} ]
}
It depends on how you are getting/creating the JSON file.
If you are creating it, then, during creation, if a value is non-existant, null, void or any other you do not desire, do not insert it.
However, if you are receiving this JSON from others, then during parse, get the key/pair value, again, if you do not want it, do not add to your object.
Without details from your program, we cant help furtherEdit 1:This code in PHP is (adapted from the one I use):
$input = file_get_contents($name, "r");
if($input != FALSE) {
for($i=0;$i<strlen($input);$i++){
if($input[$i]=='{'){
$begin= $i;
} else if($input[$i]=='}'){
$buffer .= substr($input,$begin,$i-$begin+1) .",";
if (strpos($buffer,'":null') !== false) {
// Drop the current line
} else {
// Line is correct, keep on writting
}
if($counter>=100 ){
$counter= 0;
save($buffer);
$buffer = "";
}
} else if($input[$i]==']'){
save($buffer);
$buffer = "";
$i = strlen($input);
}
}
}
In it, we will first successfully get the JSON file. Then, open the file, read each "section" (or object) of a JSON structure, then do as we must (in my case, I would fix the input, but I guess you can just drop it.)
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();
I have this tree of objects
A
B extends A
C extends B
D extends B
E extends C
F extends A and has one reference to A
A has the following annotation
#JsonTypeInfo(use=JsonTypeInfo.Id.CLASS,include=JsonTypeInfo.As.PROPERTY,property="#class")
If i try to deserialize a JSON array of objects that extends A, it throws the following error
org.codehaus.jackson.map.JsonMappingException: Unexpected token
(START_OBJECT), expected VALUE_STRING: need JSON String that contains
type id (for subtype of java.util.Collection)
The json string is generated by toString() method of a set and the set is parametric to type A where A is serialized in JSON with the following code:
ObjectMapper objectMapper=new ObjectMapper();
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_CONCRETE_AND_ARRAYS);
String res="";
try {
res = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(t);
} catch (JsonGenerationException e) {
e.printStackTrace();
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return res;
The code to deserialize the json array (that is the set described above) is:
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_CONCRETE_AND_ARRAYS);
Collection<T> results=null;
try {
results = mapper.readValue(json, TypeFactory.defaultInstance().constructParametricType(Collection.class, clazz ) );
} catch (JsonParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JsonMappingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return results;
The json sample that it parses is like:
"[{
"#class" : "pack1.pack2.MyClass",
"id" : null,
"f1" : "",
"f2" : 0.9933817827,
"f3" : 6.883261E-4,
"f4" : 0.001375699,
"f5" : {
"#class" : "pack1.pack2.MyClass2",
"id" : null,
"f1" : "",
"f2" : 0.0,
"f3" : 0.0,
"f4" : 0.0,
"f5" : [ "java.util.HashSet", [ 0 ] ],
"f6" : [ "java.util.HashSet", [ 2 ] ],
"f7" : [ "java.util.ArrayList", [ "scelta", "brani", "buona" ] ],
"f8" : [ null, "NOM", null ],
"f9" : false
},
"f10" : [ "java.util.HashMap", {
"2" : "ADJ"
} ],
"f11" : [ "java.util.HashSet", [ 0 ] ],
"f12" : [ "java.util.HashSet", [ 2 ] ],
"f13" : [ "java.util.ArrayList", [ "scelta", "brani", "buona" ] ],
"featureIndicator" : false
}]"
Here the json string includes only some objects of my sample of java Set
I believe the problem is with the default typing. The start of your JSON is not generated as what Jackson expect with default typing. The begining of the JSON should be:
["java.util.HashSet", [{
and the end should have an extra closing bracket }]].
That's because you generate the JSON using the toString() method of your set. You should instead use the ObjectMapper, that's configured with default typing, like so:
res = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(yourSet);