I have a JSON string:
{
"fruit": {
"weight":"29.01",
"texture":null
},
"status":"ok"
}
...that I am trying to map back into a POJO:
public class Widget {
private double weight; // same as the weight item above
private String texture; // same as the texture item above
// Getters and setters for both properties
}
The string above (that I am trying to map) is actually contained inside an org.json.JSONObject and can be obtained by calling that object's toString() method.
I would like to use the Jackson JSON object/JSON mapping framework to do this mapping, and so far this is my best attempt:
try {
// Contains the above string
JSONObject jsonObj = getJSONObject();
ObjectMapper mapper = new ObjectMapper();
Widget w = mapper.readValue(jsonObj.toString(), Widget.class);
System.out.println("w.weight = " + w.getWeight());
} catch(Throwable throwable) {
System.out.println(throwable.getMessage());
}
Unfortunately this code throws an exception when the Jackson readValue(...) method gets executed:
Unrecognized field "fruit" (class org.me.myapp.Widget), not marked as ignorable (2 known properties: , "weight", "texture"])
at [Source: java.io.StringReader#26c623af; line: 1, column: 14] (through reference chain: org.me.myapp.Widget["fruit"])
I need the mapper to:
Ignore the outer curly brackets ("{" and "}") altogether
Change the fruit to a Widget
Ignore the status altogether
If the only way to do this is to call the JSONObject's toString() method, then so be it. But I'm wondering if Jackson comes with anything "out of the box" that already works with the Java JSON library?
Either way, writing the Jackson mapper is my main problem. Can anyone spot where I'm going wrong? Thanks in advance.
You need to have a class PojoClass which contains (has-a) Widget instance called fruit.
Try this in your mapper:
String str = "{\"fruit\": {\"weight\":\"29.01\", \"texture\":null}, \"status\":\"ok\"}";
JSONObject jsonObj = JSONObject.fromObject(str);
try
{
// Contains the above string
ObjectMapper mapper = new ObjectMapper();
PojoClass p = mapper.readValue(jsonObj.toString(), new TypeReference<PojoClass>()
{
});
System.out.println("w.weight = " + p.getFruit().getWeight());
}
catch (Throwable throwable)
{
System.out.println(throwable.getMessage());
}
This is your Widget Class.
public class Widget
{ private double weight;
private String texture;
//getter and setters.
}
This is your PojoClass
public class PojoClass
{
private Widget fruit;
private String status;
//getter and setters.
}
Related
I need to parse a json object using the Jackson library and apply some custom logic during deserialization.
The shape of the object I need to deserialize is the following:
{
"innerObj": {
// This property may or may not be present.
// If missing, I want to set it to a custom value
"injectedStr": "value from json",
// ...other properties...
}
// ...other properties...
}
The code I have written is the following:
public class TestEmptyObject {
public static class OuterObject {
#JsonSetter(nulls = Nulls.AS_EMPTY) public InnerObject innerObj;
}
public static class InnerObject {
#JacksonInject("injectedStr") public String injectedStr;
}
#Test
public void testJacksonInject() throws IOException {
final InjectableValues injectableValues = new InjectableValues.Std()
.addValue("injectedStr", "injected value");
final ObjectMapper mapper = new JsonMapper().setInjectableValues(injectableValues);
final String json1 = "{\"innerObj\": null}";
final OuterObject outerObj1 = mapper.readValue(json1, OuterObject.class);
System.out.println(outerObj1.innerObj); // not null (OK)
System.out.println(outerObj1.innerObj.injectedStr); // null (NOT OK, should be "injected value")
final String json2 = "{}";
final OuterObject outerObj2 = mapper.readValue(json2, OuterObject.class);
System.out.println(outerObj2.innerObj); // null (NOT OK, should be {"injectedStr": "injected value"})
System.out.println(outerObj2.innerObj.injectedStr); // throws NullPointerException (obviously)
}
}
My questions:
How can I tell Jackson to build an "empty" instance of InnerObject when property innerObj is missing or null?
How can I tell Jackson how the empty instance of InnerObject should be built? Does it always default to new InnerObject() or can I specify a custom behaviour?
How can I inject my custom value for the injectedStr property when it is missing? (including when the whole innerObj is missing or null)?
Thank you a lot!
This is my JsonObject
JSONObject input = new JSONObject("{\n" +
" \"ColumnNames\":[\"col1\", \"col2\", \"col3\", \"col4\", \"col5\"]\n" +
"}");
My POJO Class
public class RequestClass {
private List<String> ColumnNames;
public void setColumnNames(List<String> ColumnNames) {
this.ColumnNames = ColumnNames;
}
public List<String> getColumnNames() {
return this.ColumnNames;
}
}
Trying to convert JsonObject to pojo class object with the help of ObjectMapper as shown below -
ObjectMapper mapper = new ObjectMapper();
//mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
RequestClass request = null;
try {
request = mapper.readValue(input.toString(), RequestClass.class);
} catch (Exception e) {
e.printStackTrace();
}
Getting an exception in output
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "ColumnNames" (class RequestClass), not marked as ignorable (one known property: "columnNames"])
at [Source: {"ColumnNames":["col1","col2","col3","col4","col5"]}; line: 1, column: 17] (through reference chain: RequestClass["ColumnNames"])
The name of the private property named ColumnNames is actually irrelevant. The property is found by introspection, looking at the getters and setters. And by convention, if you have methods named getColumnNames and setColumnNames, they define a property named columnNames (lowercase c).
So you have two choices:
change the name of the property in the JSON to columnNames, or
use an annotation to override the default introspective behavior.
The latter is achieved by using the #JsonProperty on the getter and setter, as follows:
#JsonProperty("ColumnNames")
public List<String> getColumnNames() {
return this.ColumnNames;
}
Looking at the exception it looks like in the pojo , you have mentioned ColumnNames and in the json you have mentioned columnNames (a case mismatch) , although you have defined it correctly in the json example above. Please check whether there is a case mismatch in the field names.
I have a java class representing a JSON using Jackson. All of the fields, with one exception, can be translated using no annotations at all. 1-to-1, simple translations (although some of them are nested POJOs).
#Data
#AllArgsConstructor
#NoArgsConstructor
public class MyPojo {
private String someString;
private AnotherPojo someOtherPojo;
//The problem child:
private Object value;
}
The field value which is an exception to this rule, can represent any JSON field matching value* where * is a wildcard of indefinite length. That means valueString or valueReference in JSON will be assigned to this field with the assertion that only one may be present.
{
"someString": "asdasdadasdsa",
"someOtherPojo": {
"someOtherProperty": "whatever"
},
"valueCodeableConcept": {
"text": "text value",
"coding": [
{
"code": "my-code"
}
]
}
}
Using a custom deserializer on the top-level class, I can scrape all of the fields from the root node (baseNode in the following example) that start with value and set the value field appropriately. That works great! However, in doing so, I now have to set every other field in this MyPojo class manually in my deserializer, and I have to put a custom copy of this deserializer on each POJO that uses a field like value*.
private Object parseValueX(JsonNode baseNode, DeserializationContext context) throws IOException {
//Find the concrete implementation referred to by the value[x] field
Set<String> concreteNames = new HashSet<>();
baseNode.fieldNames().forEachRemaining(name -> {
if (name.startsWith("value")) {
concreteNames.add(name);
}});
if (concreteNames.isEmpty()) {
return null;
}
if (concreteNames.size() > 1) {
throw JsonMappingException.from(context, "The field value[x] must have no more than one concrete " +
"implementation, ex: valueCode, valueCodeableConcept, valueReference");
}
String concreteName = concreteNames.stream().findFirst().orElseThrow(() -> new RuntimeException(""));
JsonNode jsonSource = baseNode.get(concreteName);
//...deserialize from jsonSource, solved, but not relevant to question...
}
To make this apply to any value* property on any POJO, I tried to move the deserializer to the value attribute in the POJO (whereas it's on the top-level resource now). The first flaw is that the deserializer isn't even invoked unless the JSON property exactly matches value. What I actually need is for the entire parent JSON resource to be passed to that field-specific deserializer, so that I may find the matching field and assign it -- OR -- I need to be able to have the deserializer on MyPojo only assign the one field value and allow the automatic deserialization to take care of the others. How do I do either of these?
For those curious about my motivation, I am implementing the HL7 FHIR Specification, which specifies generic attributes called value[x] (here's one example: https://www.hl7.org/fhir/extensibility.html#Extension) where [x] becomes the type of the resource.
I think a good fit for you problem is #JsonAnySetter. This method annotation tells Jackson to route unknown properties to it. the arg (in your case) is a Map containing the json tree of the unknown property. if I understand your code properly, the name of the value property contains the class name of the target Pojo. so once you have a class name, you can tell Jackson how to "deserialize" the map into an instance of the target class.
Here is an example based on the code from the question
public class MyPojo {
public String someString; // made properties into public for this example...
public AnotherPojo someOtherPojo;
public Object value;
#JsonAnySetter
public void setValue(String name, Object value) {
System.out.println(name + " " + value.getClass());
System.out.println(value);
// basic validation
if (name.startsWith("value") && value instanceof Map) {
String className = "com.company." + name.substring("value".length());
System.out.println(name + " " + value.getClass() + " " + className);
System.out.println(value);
try {
// nice of Jackson to be able to deserialize Map into Pojo :)
ObjectMapper mapper = new ObjectMapper();
this.value = mapper.convertValue(value, Class.forName(className));
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(this.value + " " + this.value.getClass());
}
}
}
public class AnotherPojo {
public String someOtherProperty;
}
public class CodeableConcept {
public String text;
public Code[] coding;
}
public class Code {
public String code;
}
I have the following 2 classes:
#JsonIgnoreProperties(ignoreUnknown = true)
public class ChangesJSON {
#JsonProperty("changes")
List<ChangeJSON> changes;
#JsonProperty("more")
Boolean more;
}
public class ChangeJSON {
#JsonProperty("epoch")
Long epoch;
#JsonProperty("payload")
Map<String, Object> payload;
}
When I try to deserialize using this test:
String test = "{\"changes\":[{\"epoch\":1441556306522,\"payload\":\"{\"to\":1}\"},{\"epoch\":1441555481524,\"payload\":\"{\"to\":-1}\"}],\"more\":false}";
#Test
public void myTest() {
ObjectMapper mapper = new ObjectMapper();
ChangesJSON result = null;
try {
result = mapper.readValue(test, ChangesJSON.class);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
assertNotNull(result);
}
I get the following exception:
com.fasterxml.jackson.databind.JsonMappingException: Can not
instantiate value of type java.util.LinkedHashMap from String value
('{'); no single-String constructor/factory method at [Source:
{"changes":[{"epoch":1441556306522,"payload":"{"to":1}"},{"epoch":1441555481524,"payload":"{"to":-1}"}],"more":false};
line: 1, column: 35] (through reference chain:
demo.ChangesJSON["changes"]->java.util.ArrayList[0]->demo.ChangeJSON["payload"])
It seems that there is an issue with the map but I thought Jackson should be able to handle maps. I get the same issue also when I change the map to Map. But I do need to support all sorts of classes as the values of the map.
You have quotes around the payload object. Try changing this part:
\"payload\":\"{\"to\":1}\"
into this:
\"payload\":{\"to\":1}
I think it's the JSON itself that has a problem. It unescapes to:
{"changes":[{"epoch":1441556306522,"payload":"{"to":1}"},{"epoch":1441555481524,"payload":"{"to":-1}"}],"more":false}
It should probably be something like:
{"changes":[{"epoch":1441556306522,"payload":{"to":1}},{"epoch":1441555481524,"payload":{"to":-1}}],"more":false}
So:
String test = "{\"changes\":[{\"epoch\":1441556306522,\"payload\":{\"to\":1}},{\"epoch\":1441555481524,\"payload\":{\"to\":-1}}],\"more\":false}";
HI!
I am working with a .json file, like this:
[{
"SourceFile": "videos/KobeAlleyOop.flv",
"ExifTool": {
"ExifToolVersion": 8.22,
"Warning": "Truncated 'mdat' data"
},
"System": {
"FileName": "KobeAlleyOop.flv",
"Directory": "videos",
"FileSize": "4.8 MB",
"FileModifyDate": "2010:06:15 14:57:24+02:00",
"FilePermissions": "rwxr-xr-x"
},
"File": {
"FileType": "MP4",
"MIMEType": "video/mp4"
}]
I made a Bean with 3 components:
public class MetadataContentBean {
SourceFileBean sourceFileBean;
FileBean fileBean;
SystemBean systemBean;
public FileBean getFileBean() { return fileBean; }
#JsonProperty("File")
public void setFileBean(FileBean fileBean) {
this.fileBean = fileBean; }
public SystemBean getSystemBean() {
return systemBean; }
#JsonProperty("System")
public void setSystemBean(SystemBean systemBean) {
this.systemBean = systemBean; }
public SourceFileBean
getSourceFileBean() {
sourceFileBean.getSource(); return
sourceFileBean; }
#JsonProperty("SourceFile")
public void setSourceFileBean(SourceFileBean
sourceFileBean) {
this.sourceFileBean = sourceFileBean;
} }
And I add an example of SourceFileBean, the others are similar:
public class SourceFileBean {
private String source;
public String getSource() {
return source;
}
#JsonProperty("SourceFile")
public void setSource(String source) {
this.source = source;
}
}
In the main program I make this call:
InputStream is = this.getClass().getClassLoader().getResourceAsStream(filename);
String jsonTxt = IOUtils.toString(is);
JSONArray json = (JSONArray) JSONSerializer.toJSON(jsonTxt);
JSONObject metadatacontent = json.getJSONObject(0);
ObjectMapper mapper = new ObjectMapper(); mapper.readValue(metadatacontent.toString(),MetadataContentBean.class);
But I get this error when I run it, I don't know why:
org.codehaus.jackson.map.JsonMappingException:
Can not construct instance of
com.path.bean.SourceFileBean,
problem: no suitable creator method
found at [Source:
java.io.StringReader#12d7a10; line: 1,
column: 2] at
org.codehaus.jackson.map.JsonMappingException.from(JsonMappingException.java:159)
at
org.codehaus.jackson.map.deser.StdDeserializationContext.instantiationException(StdDeserializationContext.java:212)
at
org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromString(BeanDeserializer.java:415)
at
org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:291)
at
org.codehaus.jackson.map.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:135)
at
org.codehaus.jackson.map.deser.SettableBeanProperty$MethodProperty.deserializeAndSet(SettableBeanProperty.java:221)
at
org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:390)
at
org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:286)
at
org.codehaus.jackson.map.ObjectMapper._readMapAndClose(ObjectMapper.java:1588)
at
org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1116)
at
com.path.parser.JSon.Parser(JSon.java:65)
at
com.path.parser.JSon.main(JSon.java:29)
Any help?? Thanks in advance!
I'm guessing that this is just because your JSON represents an array, with a single object inside it. You're asking Jackson to deserialize this array data onto a single instance of MetadataContentBean, which it can't do.
Try removing the [] brackets from around the JSOn, and try again.
The problem was about sintaxis and the way of writting the fields in my program.
You must be absotuely sure that it is the SAME as in the json file.
On the other hand
"SourceFile": "videos/KobeAlleyOop.flv"
is a field with just one field, so is not neccesary make a bean for it.
It is a stupid error which could make you waist a lot of time!!! :s
One problem is that you have unnecessary code in there: lines 3 and 4 are not needed and could cause issues. So just do:
InputStream is = this.getClass().getClassLoader().getResourceAsStream(filename);
String jsonTxt = IOUtils.toString(is);
ObjectMapper mapper = new ObjectMapper();
MetadataContentBean[] beans = mapper.readValue(metadatacontent.toString(),MetadataContentBean[].class);
so you don't have to use json.org's parser in there. This may not explain exact problem but helps avoid secondary issues.
But the specific problem that throws exception is simple(r): JSON value for type is String, but you are trying to make an Object (bean) out of it.
To make it work, add a public constructor that takes one String argument, and it should work.
You can annotate it with #JsonCreator if you want (or if it's not public constructor), but that should not be necessary.
Conversely, if you want to serialize a bean as JSON String, you need to do something like
#JsonValue public String asString() { return valueOfThisAsString; }