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.
Related
Am trying to deserialize a complex JSON structure using GSON. The API provider complicates things by providing an array in the results with a random name.
This is the (simplified/generified) JSON:
{
"field_1": "value",
"field_2": "value",
"field_3": {
"RANDOM_NAME": [
{
"array_field_1": "value",
"array_field_2": "value",
"array_field_3": "value"
},
{
"array_field_1": "value",
"array_field_2": "value",
"array_field_3": "value"
}
]
},
"field_4": "value"
}
and this is the corresponding (highly simplified) POJO:
public class responseObject {
String field_1;
String field_2;
Field3 field_3;
String field_4;
class Field3{
ArrayObject[] arrayObjects;
}
class ArrayObject{
String array_field_1;
String array_field_2;
String array_field_3;
}
}
However, when i run responseObject response = new Gson().fromJson(getJSON(),responseObject.class); i get the following call stack:
indicating that field_3 was not properly deserialized and does not contain an array of ArrayObject.
In this post the answers reference how to convert the data to a map, but in my case the data structure of each item in the array is actually much larger than this simplified example, and it defeats the purpose of using GSON if i have to manually pick the data i need out of a complex list of nested maps. also having trouble getting these answers to work in my scenario where the random object is an array an not a plain json object.
how do i get the randomly named array in the JSON to properly deserialize into the variable responseObject.Field3.arrayObjects??
You can avoid the complexity of using a TypeAdapeter by making the type of field_3 Map<String, List<ArrayObject>>
public class responseObject {
String field_1;
String field_2;
Map<String, List<ArrayObject>> field_3;
String field_4;
class ArrayObject{
String array_field_1;
String array_field_2;
String array_field_3;
}
}
And then to get the first item out of the Map without knowing its key you can use:
public List<ResponseObject.ArrayObject> getFirstValue(Map<String, List<ResponseObject.ArrayObject>> field_3) {
return field_3.values().iterator().next();
}
This can be solved by writing a custom TypeAdapter for Field3 which ignores the name of the property and only reads the value. The TypeAdapter has to be created by a TypeAdapterFactory to allow getting the delegate adapter for ArrayObject[]:
class Field3TypeAdapterFactory implements TypeAdapterFactory {
public Field3TypeAdapterFactory() {
}
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
// Only support Field3 class
if (type.getRawType() != Field3.class) {
return null;
}
TypeAdapter<ArrayObject[]> fieldValueAdapter = gson.getAdapter(ArrayObject[].class);
// Cast is safe, check at beginning made sure type is Field3
#SuppressWarnings("unchecked")
TypeAdapter<T> adapter = (TypeAdapter<T>) new TypeAdapter<Field3>() {
#Override
public void write(JsonWriter out, Field3 value) throws IOException {
throw new UnsupportedOperationException("Serialization is not supported");
}
#Override
public Field3 read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
in.beginObject();
// Skip the random property name
in.skipValue();
ArrayObject[] fieldValue = fieldValueAdapter.read(in);
in.endObject();
Field3 object = new Field3();
object.arrayObjects = fieldValue;
return object;
}
};
return adapter;
}
}
You can then either register the factory with a GsonBuilder, or you can annotate your Field3 class with #JsonAdapter. When using #JsonAdapter the factory class should have a no-args constructor.
I'm trying to map a json object from my dynamodb table using DynamoDbMapper and with the latest aws android sdk: com.amazonaws:aws-android-sdk-ddb-mapper:2.13.0, I'm seeing this exception: "DynamoDBMappingException: Expected S in value...
The json object in my table has 3 attributes, 2 of which are string and the third is a list of complex objects. I've created an object using the #DynamoDbDocument annotation for the complex object and used the proper marshaling annotation but it doesn't seem to be unmarshaling the json object into a java object correctly.
The complex object is a json object in this format:
{
"allCitiesList": [
{
"city": "Auckland, New Zealand",
"times": {
"recTimes": [
"Jan1",
"Jan2"
]
}
}
}
public class CitiesDO {
private String city;
private String country;
private List<AllCitiesObject> allCitiesList;
...get/setters for other fields...
#DynamoDBMarshalling(marshallerClass =
AllCitiesJSONMarshaller.class)
public List<AllCitiesObject> getAllCitiesList() {
return allCitiesList;
}
public void setAllCitiesList(List<AllCitiesObject> allCitiesList) {
this.allCitiesList = allCitiesList;
}
}
#DynamoDBDocument
public class AllCitiesObject {
#DynamoDBAttribute(attributeName = "allCitiesList")
private String data;
public AllCitiesObject(){}
public String getData() {
return data.toString();
}
public void setData(String data) {
this.data = data;
}
}
class AllCitiesJSONMarshaller extends JsonMarshaller<AllCitiesObject> {}
Have also tried this approach with a custom marshaller but no success:
public class MyCustomMarshaller implements DynamoDBMarshaller<List<AllCitiesObject>> {
private static final ObjectMapper mapper = new ObjectMapper();
private static final ObjectWriter writer = mapper.writer();
#Override
public String marshall(List<AllCitiesObject> obj) {
try {
return writer.writeValueAsString(obj);
} catch (JsonProcessingException e) {
throw new RuntimeException(
"Unable to marshall the instance of " + obj.getClass()
+ "into a string", e);
}
}
#Override
public List<AllCitiesObject> unmarshall(Class<List<AllCitiesObject>> clazz, String json) {
final CollectionType
type =
mapper.getTypeFactory().constructCollectionType(List.class, AllCitiesObject.class);
try {
return mapper.readValue(json, type);
} catch (Exception e) {
throw new RuntimeException("Unable to unmarshall the string " + json
+ "into " + clazz, e);
}
}
}
The exception is:
DynamoDBMappingException: Expected S in value {L: [{M: {times={M: {recTimes={L: [{S: Jan1,}, {S: Jan2,}
I'm having difficulty unmarshalling the json to a string although I think I have it set up correctly. Can anyone please help me understand what I'm missing and how to approach this issue? I would really appreciate your help!
DynamoDBMarshalling is deprecated, so I suggest using the newer DynamoDBTypeConverted annotation.
There are some useful notes on Mapping Arbitrary Data.
You can also see an example of mine in this answer
In summary, you create an AllCities plain java object. You then write a simple converter class which tells DynamoDB how to turn your AllCities object into a string to get into DynamoDB. Similarly, the converter class tells your application how to turn the string back into a Java object.
If anyone else is absolutely stuck on this issue with the ddbMapper, consider using the ddbClient to explicitly convert and map your DO object with your ddb table data. Due to time constraints, I'll come back to this and figure out the mapping issue at a later time and post the answer here in case it helps anyone else.
In my Android app I have json, which looks like :
{
"Records": [
{
"RowIndex": "0",
"NameValue": {
"Name": "PropertyName1",
"Value": "PropertyValue1"
}
}{
"RowIndex": "1",
"NameValue": {
"Name": "PropertyName2",
"Value": "PropertyValue2"
}
}
]
}
I need to parce this json to object, which looks like:
public class MyClass {
public String PropertyName1;
public String PropertyName2;
}
And result after parsing should be:
public String PropertyName1 = "PropertyValue1";
public String PropertyName2 = "PropertyValue2";
Basically, the first json is equivalent of:
{
"PropertyName1" : "PropertyValue1",
"PropertyName2" : "PropertyValue2"
}
Question: How can I parce first json without usage swith/case to search for the necessary Property?
You'll have to go down the dark path of reflection I'm afraid.
you can parse the json into an intermediary object which has a map for namevalue.
then you use the below code (ofcourse just copy paste the bits you need) to loop over the map of key/value pairs. for each key look up the field you want, and set it. If you're guaranteed only to need to set public variables then you can use getFields and can skip the setAccessible.
public class Test {
public static void main(String[] argv) {
MyClass myClass = new MyClass();
Class<?> classObject = myClass.getClass();
// Field fields[] = classObject.getFields(); // if you want to get only public fields.
Field fields[] = classObject.getDeclaredFields(); // any field
for(Field f : fields) {
System.out.println(f.getName());
try {
// if member is private: security managers may object but the default java allows it
f.setAccessible(true);
f.set(myClass, "abc");
} catch (IllegalAccessException e) {
// handle access exception:
e.printStackTrace();
}
}
System.out.println("prop 1: " + myClass.PropertyName1);
System.out.println("prop 2: " + myClass.PropertyName2);
}
public static class MyClass {
public String PropertyName1;
private String PropertyName2;
}
}
Actually.. there is a non-reflect way but that will replace your implementation of the object you have.
If you change your class:
public class MyClass {
public String PropertyName1;
public String PropertyName2;
}
to
public class MyClass {
private Map<String, String> properties = new HashMap<String, String>();
public void setProperties(Map<String, String> props) { this.properties = props; }
public String getPropertyName1() {
return lookupProperty("PropertyName1");
}
public String getPropertyName2() {
return lookupProperty("PropertyName2");
}
private String lookupProperty(String property) {
if (properties.containsKey(property) {
return properties.get(property);
} else {
return null;
}
}
}
then you could parse the name value map into a map, and construct a myclass with it.
just listing it for completeness, though changing your domain model to fit a json input is not ideal.
I would recommend either way to do the input parsing, and then copy over the model into your actual domain object rather than using the json-model in your application. that way if the json model ever changes, your domain model will not change.
One method I can think of (which doesn't sound too great) is to actually make an object that matches the JSON response you get back. Then, map THAT NameValue object to MyClass
So, something like
public class NameValue {
public string Name;
public String Value;
public MyClass getMyClass(){
MyClass myClass = new MyClass();
myClass.PropertyName2 = Value;
return myClass;
}
}
You can come up with a better way to map it, obviously. But this is just an example of something I might do if I was given a response JSON I didn't particularly care for. You can similarly reverse it (have MyClass be able to create a NameValue object) so you can send data back in the correct format.
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
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