Parse JSON on Android device using latest Jackson Parser library - java

Here is example of JSON response str:
{"myServiceMethodResult":[{"BoolPropertyOfFooClass":false,"StringPropertyOfFooClass":"tstString", "Bar":[{"BoolPropertyOfBarClass":false,"StringProperyOfBarClass":"tst"}]
}]
}
Service is returning List
List<Foo> myServiceMethod(){
return new List<Foo> myFooList
}
This are the classes:
#JsonRootName(value = "myServiceMethodResult")
Class Foo{
public boolean BoolPropertyOfFooClass
public String StringPropertyOfFooClass
#JsonProperty(value = "Bar")
public List<Bar> myBar;
public boolean getBoolPropertyOfFooClass(){
return BoolPropertyOfFooClass;
}
public void setBoolPropertyOfFooClass(bool value){
this.BoolPropertyOfFooClass = value
}
public String getStringPropertyOfFooClass(){
return StringPropertyOfFooClass;
}
public void setBoolPropertyOfFooClass(String value){
this.StringPropertyOfFooClass = value
}
public List<Bar> myBar() {
return myBar;
}
public void setmyBar(List<Bar> value) {
this.myBar= value;
}
}
I'm usign Jackson parser and first of all Parsing JSON string to an object is surprising slow (despite a fact that this file is huge (2 MB)
String jsonStr = sh.makeServiceCall(serviceUrl/MethodName, ServiceHandler.POST, json_content_parameters);
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
JsonNode node = null;
myFooInstance= mapper.readValue(new StringReader(jsonStr), new TypeReference<List<Foo>>(){});
mapper.readValue is hitting exception myServiceResult does not match expected ('List'). Further more, if I'm using readTree function it takes 5 seconds (but not hittign exception). Is there any better way of getting Object faster,
Further more I'm not able to figure how to map List of Bar objects inside my Foo objects. I'm able to set my properties using this line of code:
TypeReference<List<Foo>> typeRef = new TypeReference<List<Foo>>(){};
myInstanceFoo= mapper.readValue(node.traverse(), typeRef);
So I Have my List of Foo objects but I'm not able to get List inside of list using something simmilar. Any help about problems with duration, or setting inner List object would be appreciated
Trace:
com.fasterxml.jackson.databind.JsonMappingException: Root name 'MyMethodResponse' does not match expected ('List') for type [collection type; class java.util.List, contains [simple type, class com.package.Foo]]
at [Source: java.io.StringReader#411dc790; line: 1, column: 2]

Since it appears that you have the response wrapped in a single-member object instance, you have the option of annotating your Foo class with this:
#JsonRootName("MyMethodResponse")
IMPORTANT: the name is FIXED.
However you are not done yet. You need to configure your ObjectMapper to use this annotation:
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE);
Your have another problem. Namely, your List<Bar> has name myBar in your POJO, but Bar in the produced JSON. You need to annotate your myBar field with #JsonProperty:
#JsonProperty("Bar")

In case Someone stumbles on a same problem I figured it out. To serialize Foo class if JSON is in format
{"response":[{"propertyOfFooClass":"something"
}]
}
you nedd to Create Root Class that contains list of Foo Class
public class RootWrapper {
private List<Foo> foo;
public List<Foo> getFoos() {
return channels;
}
#JsonProperty("response")
public void setFoos(List<Foo> fooList) {
this.foo= fooList;
}
RootWrapper mj = mapper.readValue(jsonStr, RootWrapper.class);
Cheers

Related

Deserialization of different objects Android

I have a problem with deserialization list of different objects. Help me please to resolve this issue. This JSON is required by the customer side.
{"result":[
{
"id": 5,
"op":[
0,
{ "description": "hello world" }
]}]
}
I have:
public class Transaction {
public int id;
public List<Object> op;
}
public class ResponseTransactions {
public List<Transaction> result;
}
Gson gson = new Gson();
List< List<Transaction>> list= gson.fromJson(json,
ResponseTransactions.class))
After that I must call LinkedTreeMap:
String description = (LinkedTreeMap)Transaction.op.get(1).get("description");
But I want to use like this:
public class Operation{
public String description;
}
public class Transaction {
public String id;
public List<Operation> op;
}
I am not sure why you would have a dissimilar collection of objects cast into a list of concrete objects , but if thats what is required, you might want to look at a custom Deserializer. Here's a very informative link on how to create a custom deserializer for gson lib.
https://futurestud.io/tutorials/gson-advanced-custom-deserialization-basics
In your deserializer, you'll need to skip any JsonElement which is not of type "Operation"

Deserializing flattened JSON to Java Object using Jackson

So I am currently using Jackson to deserialize JSON into complex java objects. Everything works well but I also have some fields such as:
{
"foo.bar.baz":"qux"
}
which correspond to java objects such as:
class Foo {
AnotherClass bar;
}
class AnotherClass {
String baz;
}
Jackson is unable to figure out that the dots correspond to inner objects. Is there a way to get Jackson to be able to deserialize even on flattened fields such as the field in my example?
No Jackson JSON library will not detect this as different object levels. You can use this instead:
{
"foo": {
"bar": {
"baz":"qux"
}
}
}
And you will have to create:
Class WrapperClass containing "foo" of type FooClass
Class FooClass containing "bar" of type BarClass
Class BarClass containing "baz" of type String
You can do something like that by using #JsonUnwrapped:
public class Wrapper {
#JsonUnwrapped(prefix="foo.bar.")
public AnotherClass foo; // name not used for property in JSON
}
public class AnotherClass {
String baz;
}
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(jsonString);
Iterator<String> iterator = root.fieldNames();
while (iterator.hasNext()) {
String fieldName = iterator.next();
if (fieldName.contains(".")) {
String[] items = fieldName.split("\\.");
if (items[0].equals("foo")) {
Foo foo = new Foo();
if (items[1].equals("bar")) {
AnotherClass bar = new AnotherClass();
foo.bar = bar;
if (items[2].equals("baz")) {
bar.baz = root.get(fieldName).asText();
}
}
}
}
}

jackson delay deserializing field

I have a class like this:
public class DeserializedHeader
int typeToClassId;
Object obj
I know what type of object obj is based on the typeToClassId, which is unfortunately only known at runtime.
I want to parse obj out based on typeToClassId - what's the best approach here? Annotations seem like they're out, and something based on ObjectMapper seems right, but I'm having trouble figuring out what the best approach is likely to be.
Something along the lines of
Class clazz = lookUpClassBasedOnId(typeToClassId)
objectMapper.readValue(obj, clazz)
Obviously, this doesn't work since obj is already deserialized... but could I do this in 2 steps somehow, perhaps with convertValue?
This is really complex and painful problem. I do not know any sophisticated and elegant solution, but I can share with you my idea which I developed. I have created example program which help me to show you how you can solve your problem. At the beginning I have created two simple POJO classes:
class Product {
private String name;
// getters/setters/toString
}
and
class Entity {
private long id;
// getters/setters/toString
}
Example input JSON for those classes could look like this. For Product class:
{
"typeToClassId" : 33,
"obj" : {
"name" : "Computer"
}
}
and for Entity class:
{
"typeToClassId" : 45,
"obj" : {
"id" : 10
}
}
The main functionality which we want to use is "partial serializing/deserializing". To do this we will enable FAIL_ON_UNKNOWN_PROPERTIES feature on ObjectMapper. Now we have to create two classes which define typeToClassId and obj properties.
class HeaderType {
private int typeToClassId;
public int getTypeToClassId() {
return typeToClassId;
}
public void setTypeToClassId(int typeToClassId) {
this.typeToClassId = typeToClassId;
}
#Override
public String toString() {
return "HeaderType [typeToClassId=" + typeToClassId + "]";
}
}
class HeaderObject<T> {
private T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
#Override
public String toString() {
return "HeaderObject [obj=" + obj + "]";
}
}
And, finally source code which can parse JSON:
// Simple binding
Map<Integer, Class<?>> classResolverMap = new HashMap<Integer, Class<?>>();
classResolverMap.put(33, Product.class);
classResolverMap.put(45, Entity.class);
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
String json = "{...}";
// Parse type
HeaderType headerType = mapper.readValue(json, HeaderType.class);
// Retrieve class by integer value
Class<?> clazz = classResolverMap.get(headerType.getTypeToClassId());
// Create dynamic type
JavaType type = mapper.getTypeFactory().constructParametricType(HeaderObject.class, clazz);
// Parse object
HeaderObject<?> headerObject = (HeaderObject<?>) mapper.readValue(json, type);
// Get the object
Object result = headerObject.getObj();
System.out.println(result);
Helpful links:
How To Convert Java Map To / From JSON (Jackson).
java jackson parse object containing a generic type object.

How do I configure the the jackson objectmapper to correctly deserialize to pojo?

I'm having a bit of a problem understanding how i should configure the objectMapper and pojo when deserializing. My Json is created by another application that
supports both xml and json. It returns a list with myobject, but the Json contains the type, like this:
[
{
"myobject": {
"somethingcool": "amazing",
"contactPersonsForMyObject": [
"test.test#gmail.com",
"test#test.se"
],
"myObjectId": "c85e48730501bfae41e67714c6131b7d"
}
},
{
"myobject": {
"somethingcool": "cool",
"contactPersonsForMyObject": [
"test.test2#gmail.com",
"test#test2.se"
],
"myObjectId": "c85e48730501bfae41e67714cqwerty"
}
}
]
My class:
public class MyObject {
private String myObjectId;
private String somethingcool;
private List<String> contactPersonsForMyObject;
public String getMyObjectId() {
return myObjectId;
}
public void setMyObjectId(String myObjectId) {
this.myObjectId = myObjectId;
}
public String getSomethingcool() {
return somethingcool;
}
public void setSomethingcool(String somethingcool) {
this.somethingcool = somethingcool;
}
public List<String> getContactPersonsForMyObject() {
return contactPersonsForMyObject;
}
public void setContactPersonsForMyObject(List<String> contactPersonsForMyObject) {
this.contactPersonsForMyObject = contactPersonsForMyObject;
}
}
But when doing:
List<MyObject> myObjects = mapper.convertValue(rootNode, new TypeReference<List<MyObject>>() {});
I'm getting a exception stating:
java.lang.IllegalArgumentException: Unrecognized field "myobject" (Class com.domain.MyObject), not marked as ignorable
at [Source: N/A; line: -1, column: -1] (through reference chain: com.domain.MyObject["myobject"])
It's like the mapper do not understand the extra "layer".
When serializing to get this structure it is possible to configure the mapper like this: mapper.configure(SerializationConfig.Feature.WRAP_ROOT_VALUE, true);
So there should be somehow to do the reverse?
Thank you!
You need to give it concrete classes and not interfaces. So
List<Map<String, MyObject>> myObjects = mapper.readValue(json, new TypeReference<ArrayList<HashMap<String, MyObject>>>() {
});
What you need is to use #JsonTypeInfo annotation on type (class), which will include additional type information. In your case it looks as if you wanted to include a type id as property key.
If so, inclusion method should be "as wrapper object", and you will also need to define what type id of "myobject" binds to -- this can be done by adding #JsonTypeName("myobject") for MyObject class (it needs to be included in subtype of whatever has #JsonTypeInfo, but in this case both would be added for the same class).
Your json has an extra level of nesting: you have a list of Maps of Strings to MyObjects, not a List of MyObjects. You'd need to read it like this:
List<Map<String, MyObject>> myObjects = mapper.readValue(json, new TypeReference<List<Map<String, MyObject>>>() {
});
Or else change whatever is generating this json to ditch the inner Map (IMHO that'd be better).
Change List<String> to ArrayList<String>
and then
MyObject myObject = mapper.readValue(json, MyObject.class);
Add the following constructor to MyObject class
#JsonCreator
public MyObject(#JsonProperty("myObjectId") String myObjectId,
#JsonProperty("somethingcool") String somthingcool,
#JsonProperty("contact") ArrayList<String> contactPersonsForMyObject) {
this.myObjectID = myObjectId;
this.somethingcool = somethingcool;
this.contactPersonsForMyObject = contactPersonsForMyObject;
}
and change the return value for the getter to ArrayList

How can I include raw JSON in an object using Jackson?

I am trying to include raw JSON inside a Java object when the object is (de)serialized using Jackson. In order to test this functionality, I wrote the following test:
public static class Pojo {
public String foo;
#JsonRawValue
public String bar;
}
#Test
public void test() throws JsonGenerationException, JsonMappingException, IOException {
String foo = "one";
String bar = "{\"A\":false}";
Pojo pojo = new Pojo();
pojo.foo = foo;
pojo.bar = bar;
String json = "{\"foo\":\"" + foo + "\",\"bar\":" + bar + "}";
ObjectMapper objectMapper = new ObjectMapper();
String output = objectMapper.writeValueAsString(pojo);
System.out.println(output);
assertEquals(json, output);
Pojo deserialized = objectMapper.readValue(output, Pojo.class);
assertEquals(foo, deserialized.foo);
assertEquals(bar, deserialized.bar);
}
The code outputs the following line:
{"foo":"one","bar":{"A":false}}
The JSON is exactly how I want things to look. Unfortunately, the code fails with an exception when attempting to read the JSON back in to the object. Here is the exception:
org.codehaus.jackson.map.JsonMappingException: Can not deserialize instance of java.lang.String out of START_OBJECT token
at [Source: java.io.StringReader#d70d7a; line: 1, column: 13] (through reference chain: com.tnal.prism.cobalt.gather.testing.Pojo["bar"])
Why does Jackson function just fine in one direction but fail when going the other direction? It seems like it should be able to take its own output as input again. I know what I'm trying to do is unorthodox (the general advice is to create an inner object for bar that has a property named A), but I don't want to interact with this JSON at all. My code is acting as a pass-through for this code -- I want to take in this JSON and send it back out again without touching a thing, because when the JSON changes I don't want my code to need modifications.
Thanks for the advice.
EDIT: Made Pojo a static class, which was causing a different error.
#JsonRawValue is intended for serialization-side only, since the reverse direction is a bit trickier to handle. In effect it was added to allow injecting pre-encoded content.
I guess it would be possible to add support for reverse, although that would be quite awkward: content will have to be parsed, and then re-written back to "raw" form, which may or may not be the same (since character quoting may differ).
This for general case. But perhaps it would make sense for some subset of problems.
But I think a work-around for your specific case would be to specify type as 'java.lang.Object', since this should work ok: for serialization, String will be output as is, and for deserialization, it will be deserialized as a Map. Actually you might want to have separate getter/setter if so; getter would return String for serialization (and needs #JsonRawValue); and setter would take either Map or Object. You could re-encode it to a String if that makes sense.
Following #StaxMan answer, I've made the following works like a charm:
public class Pojo {
Object json;
#JsonRawValue
public String getJson() {
// default raw value: null or "[]"
return json == null ? null : json.toString();
}
public void setJson(JsonNode node) {
this.json = node;
}
}
And, to be faithful to the initial question, here is the working test:
public class PojoTest {
ObjectMapper mapper = new ObjectMapper();
#Test
public void test() throws IOException {
Pojo pojo = new Pojo("{\"foo\":18}");
String output = mapper.writeValueAsString(pojo);
assertThat(output).isEqualTo("{\"json\":{\"foo\":18}}");
Pojo deserialized = mapper.readValue(output, Pojo.class);
assertThat(deserialized.json.toString()).isEqualTo("{\"foo\":18}");
// deserialized.json == {"foo":18}
}
}
I was able to do this with a custom deserializer (cut and pasted from here)
package etc;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
/**
* Keeps json value as json, does not try to deserialize it
* #author roytruelove
*
*/
public class KeepAsJsonDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException {
TreeNode tree = jp.getCodec().readTree(jp);
return tree.toString();
}
}
Use it by annotating the desired member like this:
#JsonDeserialize(using = KeepAsJsonDeserializer.class)
private String value;
#JsonSetter may help. See my sample ('data' is supposed to contain unparsed JSON):
class Purchase
{
String data;
#JsonProperty("signature")
String signature;
#JsonSetter("data")
void setData(JsonNode data)
{
this.data = data.toString();
}
}
This is a problem with your inner classes. The Pojo class is a non-static inner class of your test class, and Jackson cannot instantiate that class. So it can serialize, but not deserialize.
Redefine your class like this:
public static class Pojo {
public String foo;
#JsonRawValue
public String bar;
}
Note the addition of static
Adding to Roy Truelove's great answer, this is how to inject the custom deserialiser in response to appearance of #JsonRawValue:
import com.fasterxml.jackson.databind.Module;
#Component
public class ModuleImpl extends Module {
#Override
public void setupModule(SetupContext context) {
context.addBeanDeserializerModifier(new BeanDeserializerModifierImpl());
}
}
import java.util.Iterator;
import com.fasterxml.jackson.annotation.JsonRawValue;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
public class BeanDeserializerModifierImpl extends BeanDeserializerModifier {
#Override
public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc, BeanDeserializerBuilder builder) {
Iterator<SettableBeanProperty> it = builder.getProperties();
while (it.hasNext()) {
SettableBeanProperty p = it.next();
if (p.getAnnotation(JsonRawValue.class) != null) {
builder.addOrReplaceProperty(p.withValueDeserializer(KeepAsJsonDeserialzier.INSTANCE), true);
}
}
return builder;
}
}
This easy solution worked for me:
public class MyObject {
private Object rawJsonValue;
public Object getRawJsonValue() {
return rawJsonValue;
}
public void setRawJsonValue(Object rawJsonValue) {
this.rawJsonValue = rawJsonValue;
}
}
So I was able to store raw value of JSON in rawJsonValue variable and then it was no problem to deserialize it (as object) with other fields back to JSON and send via my REST. Using #JsonRawValue didnt helped me because stored JSON was deserialized as String, not as object, and that was not what I wanted.
This even works in a JPA entity:
private String json;
#JsonRawValue
public String getJson() {
return json;
}
public void setJson(final String json) {
this.json = json;
}
#JsonProperty(value = "json")
public void setJsonRaw(JsonNode jsonNode) {
// this leads to non-standard json, see discussion:
// setJson(jsonNode.toString());
StringWriter stringWriter = new StringWriter();
ObjectMapper objectMapper = new ObjectMapper();
JsonGenerator generator =
new JsonFactory(objectMapper).createGenerator(stringWriter);
generator.writeTree(n);
setJson(stringWriter.toString());
}
Ideally the ObjectMapper and even JsonFactory are from the context and are configured so as to handle your JSON correctly (standard or with non-standard values like 'Infinity' floats for example).
Here is a full working example of how to use Jackson modules to make #JsonRawValue work both ways (serialization and deserialization):
public class JsonRawValueDeserializerModule extends SimpleModule {
public JsonRawValueDeserializerModule() {
setDeserializerModifier(new JsonRawValueDeserializerModifier());
}
private static class JsonRawValueDeserializerModifier extends BeanDeserializerModifier {
#Override
public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc, BeanDeserializerBuilder builder) {
builder.getProperties().forEachRemaining(property -> {
if (property.getAnnotation(JsonRawValue.class) != null) {
builder.addOrReplaceProperty(property.withValueDeserializer(JsonRawValueDeserializer.INSTANCE), true);
}
});
return builder;
}
}
private static class JsonRawValueDeserializer extends JsonDeserializer<String> {
private static final JsonDeserializer<String> INSTANCE = new JsonRawValueDeserializer();
#Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
return p.readValueAsTree().toString();
}
}
}
Then you can register the module after creating the ObjectMapper:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JsonRawValueDeserializerModule());
String json = "{\"foo\":\"one\",\"bar\":{\"A\":false}}";
Pojo deserialized = objectMapper.readValue(json, Pojo.class);
I had the exact same issue.
I found the solution in this post :
Parse JSON tree to plain class using Jackson or its alternatives
Check out the last answer.
By defining a custom setter for the property that takes a JsonNode as parameter and calls the toString method on the jsonNode to set the String property, it all works out.
Using an object works fine both ways... This method has a bit of overhead deserializing the raw value in two times.
ObjectMapper mapper = new ObjectMapper();
RawJsonValue value = new RawJsonValue();
value.setRawValue(new RawHello(){{this.data = "universe...";}});
String json = mapper.writeValueAsString(value);
System.out.println(json);
RawJsonValue result = mapper.readValue(json, RawJsonValue.class);
json = mapper.writeValueAsString(result.getRawValue());
System.out.println(json);
RawHello hello = mapper.readValue(json, RawHello.class);
System.out.println(hello.data);
RawHello.java
public class RawHello {
public String data;
}
RawJsonValue.java
public class RawJsonValue {
private Object rawValue;
public Object getRawValue() {
return rawValue;
}
public void setRawValue(Object value) {
this.rawValue = value;
}
}
I had a similar problem, but using a list with a lot of JSON itens (List<String>).
public class Errors {
private Integer status;
private List<String> jsons;
}
I managed the serialization using the #JsonRawValue annotation. But for deserialization I had to create a custom deserializer based on Roy's suggestion.
public class Errors {
private Integer status;
#JsonRawValue
#JsonDeserialize(using = JsonListPassThroughDeserialzier.class)
private List<String> jsons;
}
Below you can see my "List" deserializer.
public class JsonListPassThroughDeserializer extends JsonDeserializer<List<String>> {
#Override
public List<String> deserialize(JsonParser jp, DeserializationContext cxt) throws IOException, JsonProcessingException {
if (jp.getCurrentToken() == JsonToken.START_ARRAY) {
final List<String> list = new ArrayList<>();
while (jp.nextToken() != JsonToken.END_ARRAY) {
list.add(jp.getCodec().readTree(jp).toString());
}
return list;
}
throw cxt.instantiationException(List.class, "Expected Json list");
}
}

Categories

Resources