I am trying to parse a JSON structure similar to this one:
{
"cars": {
"112": {
"make": "Cadillac",
"model": "Eldorado",
"year": "1998"
},
"642": {
"make": "Cadillac",
"model": "Eldorado",
"year": "1990"
},
"9242": {
"make": "Cadillac",
"model": "Eldorado",
"year": "2001"
}
}}
I have a CarEntity class defined with makeName,model,year attributes defined and accessible via setters/getters.
I am trying to deserialize this JSON like this:
Map<String, CarEntity> deserialized = new JSONDeserializer<Map<String, CarEntity>>()
.use("cars.values", Map.class)
.deserialize(json);
and it doesn't work :( It does deserialize it but not into Map<String, CarEntity> but rather into deep Map(something like Map<String, Map<String, Map<String, String>>> )
What am I doing wrong?
You're problem is your json has two maps. One which contains the 'cars' key, and one that contains the actual CarEntity. Unfortunately, you can't refer to a single key within a Map and assign types on just that key at this time. Generally setting types on values for collections refers to all values within the collection. You don't need to specify the types for the first Map that contains the "cars" key since it will deserialize it by default.
Map<String, CarEntity> deserialized = new JSONDeserializer<Map<String,Map<String, CarEntity>>>()
.use("values.values", CarEntity.class )
.deserialize(json).get("cars");
The path 'values.values' refers to the outer Map's values then traversing the next map values are all CarEntity instances.
I've considered changing the path expressions to be more expressive allowing you to target a single value in a collection, but this increases overhead of evaluating them and being backwards compatible is a challenge.
You are most likely being bitten by Java Type Erasure: JSON library in question does not know type you want; all it sees is equivalent of Map. So you must specify at least value type somehow. Hopefully FlexJSON documentation points out how.
Alternatively you may be able to sub-class HashMap into your own type (MyEntityMap extends HashMap), since then type information can be inferred from generic super type; and passing MyEntityMap.class would give type information that most JSON libraries can use (Jackson and GSON at least).
If these do not work, Jackson and GSON libraries can handle this use case easily; both have methods to specify generic types for deserialization.
Just add one more call to get("cars") like:
Map<String, CarEntity> deserialized = new JSONDeserializer<Map<String, CarEntity>>()
.use("cars.values", Map.class)
.deserialize(json).get("cars");
jSon string was probably serialized from a variable cars typed as Map<String, CarEntity>
Related
Want to convert incoming JSON to a Map<String, Object> but with the following requirements:
Unknow properties should not cause exceptions (objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)).
Only whitelisted properties of the incoming JSON should appear in the Map (with the above configuration even unknown properties would be present in the Map).
The whitelisted properties should automatically correspond to the fields of a POJO.
Converting the JSON directly to my POJO would take care of points 1, 2, and 3, but I would lose the knowledge of which properties were included in the incoming JSON, that's why I want to convert to a Map.
Going from JSON to POJO to Map (let's call it Map2) would also work, but then any primitive fields in the POJO would have default values in Map2, even though they were absent in the JSON.
Put another way, I want the fields in the resultant Map to be the intersection of the properties of the JSON and the fields in the POJO.
For example, given the JSON:
{
"name": "ABCD",
"_email": "abcd#example.com",
"roles": [
"USER"
]
}
where name and roles are valid fields in the POJO (email is valid, but not _email), I want to end up with the Map:
{
name: "ABCD",
roles: [ "USER"]
}
Don't want to have to hand-code the list of valid field names for the various POJOs.
Using this code
Map<String,Object> payloadMap = new HashMap<String,Object>();
payloadMap = (Map<String,Object>) new Gson().fromJson(result, payloadMap.getClass());
, I convert this json:
{
"name":"name1",
"job":"prosecutor",
"department": {
"department_name":"prosecutor's office"
}
}
to the map (map with unlimited number of child maps):
This done well, but now I want to get an access to values of child (nested) maps.
In parent map child maps "wrapped" to Object.
So, I tried to get "wrapped" child maps from the Object-values of parent map.
public void mapRequestNode (Map<String,Object> payloadMap) {
payloadMap.entrySet().forEach(node->this.getDataFromNode(node));
}
As you can see from the above picture, there are no way to use child map "department", which had been "wrapped" to Object. I have an access to Object-methods, but not to the Map-methods (for example, I cant use "value.get("department_name")". I tried cast "(Map<String, Object>)value", but without success...
The "department" name in case above is only for example! I dont know concrete name of json child-objects. There may be unlimited number of names! So I cant use something like this "payloadMap.get("department")"
Following
((Map<String, Object>)payloadMap.get("department")).get("department_name")
should work, dont?
Your variable value is of type Object, which means that the compiler will not know anything else about the variable. Even if the object you retrieve from your json file is a map, as you store it in a Object variable, the compiler will handle it as an Object and not as a Map. That is why you cannot do value.get("department"); : the method get does not exist for the type Object.
You need to cast whatever is stored in value.get("department") as a Map<String, Object> to be able to handle it as a Map.
I have found a special solution.
I convert json to Map<String,Map<String,Object>>.
Not in Map<String,Object>. In this case I can successfully use child-maps of parent dto-map.
But this solution is special (not general) in the meaning that, I can handle in this way json, which consist only of objects.
For example, if I try to get the value of "job" in following example:
{
"job": "prosecutor",
"department": {
"department_name":"prosecutor's office"
}
}
by using Map.Entry<String, Map<String, Object>> payloadNodeEntry.getValue,
I will receive ClassCastException (cant cast String to Map<String, Object> ).
I have a request json in the format:
{
"key1": "value1",
"key2": "value2",
.
..
...
"keyn": "valuen",
"generic": {
"key1":"string-type-value1"
"key2":"string-type-value2"
"key3":"complex-type-value3"
.
..
...
"keyn": "simple/complex-valuen"
}
}
As we can see, there is a property called generic .This property was initially made to accept arbitrary key-value pairs in String format only. Hence,we created a property Map<String,String>
The future requirement is such that we should also be able to accept arbitrary complex type value(such as list,array,etc) and not only String.
Is there a less-complex way to serialize/deserialize for this use-case using Jackson?
This property was initially made to accept arbitrary key-value pairs in String format only. Hence, we created a property Map<String, String>. [...] we should also be able to accept arbitrary complex type value(such as list,array,etc) and not only String.
Use a Map<String, Object> instead.
We have the following Json:
{
"type" : "1",
"otherStuff" : "2",
...
"items" : [
{
"commonItemAttribute" : "value",
"specificToType1" : "whatever"
...
}
]
}
We need to polymorphically deserialise the items into different sub classes based on the type attribute.
Is it possible in a custom Jackson deserialiser to get the type value?
Can we safely look back up the Json tree using the JsonParser given to the deserialize method?
I found this blog about polymorphic deserialisation but it seems to require a type attribute on the items themselves.
Thanks
This kind of JSON is not supported, since while "External" type ids of form:
{ "childType" : "SomeTypeId",
"child" : { .... }
}
are supported (with #JsonTypeInfo.As.EXTERNAL_PROPERTY), they only work for simple types, not for Collections or Maps.
So if you can't change JSON to be bit more standard (including type id for elements is the standard way), you will need to use custom serializers, deserializers.
I have an application which makes use of an external library (Jackson), and the method I need requires a class literal as an argument. So if I wish to parse my JSON string into a User object:
ObjectMapper mapper = new ObjectMapper();
User user = mapper.readValue(new File("user.json"), User.class);
Now, I wish to use this method dynamically (i.e. parse different JSON strings using the same line of code). For example:
String json1 = "{"type":"jacket",...}";
String json2 = "{"type":"sweater",...}";
Object object = mapper.readValue(json1/json2, ???);
//returns a Jacket object OR Sweater object based on the "type" key
//i.e. use Jacket.class as the 2nd argument if "type" is "jacket"
//OR Sweater.class if "type" is "sweater"
//After getting the deserialized object,
//if object is Jacket, cast as a Jacket
//if object is Sweater, cast as a Sweater
Of course, the JSON string in question can be for any class, so I can't simply hard-code an if-else loop. I've looked at custom serializers, but frankly am quite lost at what it's talking about, and would like some help in how I can go about this.
In summary, I need some way to first define a class literal from a String, and then cast the resulting Object into the specific class (but my focus is on getting readValue to work dynamically).
Looks like you need a mapping somewhere between JSON type variable and Java class type.
Generally result should be something like this map:
Map<String, Class<? extends YourSupertype>> map = new HashMap<>();
map.put("sweater", Sweater.class);
map.put("jacket", Jacket.class);
Just store possible clothing types somewhere in a file, then do something like:
String clothingType = nextEntryFromFile();
String className = constructClassNameFromClothingType(clothingType);
map.put(clothingType, Class.forName(className));
Since version 1.5 Jackson supports Polymorphic Type Handling, check here http://www.cowtowncoder.com/blog/archives/2010/03/entry_372.html
there are examples on how to correctly handle deserialization in those cases.