How to deal with nested static casting? - java

I simply have a Map. But it can return a Map, which may also return a Map. It's possible up to 3 to 4 nested Maps. So when I want to access a nested value, I need to do this:
((Map)((Map)((Map)CacheMap.get("id")).get("id")).get("id")).get("id")
Is there a cleaner way to do this?
The reason I'm using a Map instead of mapping it to an object is for maintainability (e.g. when there are new fields).
Note:
Map<String, Object>
It has to be Object because it won't always return a Hashmap. It may return a String or a Long.
Further clarification:
What I'm doing is I'm calling an api which returns a json response which I save as a Map.

Here's some helper methods that may help things seem cleaner and more readable:
#SuppressWarnings("unchecked")
public static Map<String, Object> getMap(Map<String, Object> map, String key) {
return (Map<String, Object>)map.get(key);
}
#SuppressWarnings("unchecked")
public static String getString(Map<String, Object> map, String key) {
return (String)map.get(key);
}
#SuppressWarnings("unchecked")
public static Integer geInteger(Map<String, Object> map, String key) {
return (Integer)map.get(key);
}
// you can add more methods for Date, Long, and any other types you know you'll get
But you would have to nest the calls:
String attrValue = getString(getMap(getMap(map, id1), id2), attrName);
Or, if you want something more funky, add the above methods as instance methods to a map impl:
public class FunkyMap extends HashMap<String, Object> {
#SuppressWarnings("unchecked")
public FunkyMap getNode(String key) {
return (FunkyMap)get(key);
}
#SuppressWarnings("unchecked")
public String getString(String key) {
return (String)get(key);
}
#SuppressWarnings("unchecked")
public Integer geInteger(String key) {
return (Integer)get(key);
}
// you can add more methods for Date, Long, and any other types you know you'll get
}
Deserialize into this class with your json library (you'll probably have to provide it with a factory method for the map class impl), then you can chain the calls more naturally:
String attrValue = map.getNode(id1).getNode(id2).getString(attrName);
The funky option is what I did for a company, and it worked a treat :)

If you don't know the depth of the JSON tree and if you worry about maintainability if new fields are added, I would recommend not to deserialize the full tree in a Map but instead use a low-level parser.
For example, if your JSON looks like the following:
{
"id": {
"id": {
"id": {
"id": 22.0
}
}
}
}
You could write something like that to get the id using Jackson:
public Object getId(String json) throws JsonParseException, IOException
{
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(json);
JsonNode id = root.get("id");
while (id != null && id.isObject())
{
id = id.get("id");
}
//Cannot find a JsonNode for the id
if (id == null)
{
return null;
}
//Convert id to either String or Long
if (id.isTextual())
return id.asText();
if (id.isNumber())
return id.asLong();
return null;
}

Related

Jackson: serialize empty or null HashMap to empty Array

I have this class whose attributes should be serialized to JSon using Jackson:
public class MicroClinicalInfo {
#JsonProperty
private Map<String, String> clinicalInfo;
#JsonCreator
public MicroClinicalInfo() {}
public MicroClinicalInfo(DtoMicroSampleInfo dto) {
this.clinicalInfo = new HashMap<String, String>();
for(/* each clinical info entity received from EJB in dto */) {
this.clinicalInfo.put(clinicalInfoKey, clinicalInfoDescr);
}
}
}
Now, if clinical info do not exist, produced JSon is {"clinicalInfo":{}} but in this case frontend developer wants me to return an empty array and not an empty object. So, I should return {"clinicalInfo":[]}. How can I do that?
I was trying with:
#JsonGetter
public Map<String, String> getClinicalInfo() {
if (this.clinicalInfo != null && !this.clinicalInfo.isEmpty()) {
return this.clinicalInfo;
}
else {
}
}
but I don't know how to give a uniform vision. Because serialization of a not empty HashMap is correct, but I don't know how to render it as an empty array.
Thank you all

Mapping fields as key-value pair

When reading a JSON file, i would like to map my class as follows:
public class Effect {
private final String type;
private final Map<String, String> parameters;
public Effect(String type, Map<String, String> parameters) {
this.type = type;
this.parameters = parameters;
}
public String getType() {
return this.type;
}
public Map<String, String> getParameters() {
return this.parameters;
}
}
{
"type": {
"key1": "value1",
"key2": "value2",
}
}
So, the mapped JSON object consists of type as the only key and parameters as its value.
I would like to use #JsonCreator on the constructor, but can't figure out, how to map the fields. Do i need to write a custom deserializer or is there an easier way to map the class like i want?
I wrote a custom deserializer, which does what i want, but there might be an easier way, maybe with annotations alone, which i would like to know:
public class EffectDeserializer extends StdDeserializer<Effect> {
private static final long serialVersionUID = 1L;
public EffectDeserializer() {
super(Effect.class);
}
#Override
public Effect deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException {
JsonNode node = parser.getCodec().readTree(parser);
Iterator<String> fieldNames = node.fieldNames();
if(fieldNames.hasNext()) {
String type = fieldNames.next();
Map<String, String> parameters = new HashMap<>();
for(Iterator<Entry<String, JsonNode>> fields = node.get(type).fields(); fields.hasNext(); ) {
Entry<String, JsonNode> field = fields.next();
parameters.put(field.getKey(), field.getValue().textValue());
}
return new Effect(type, parameters);
}
return null;
}
}
Another way i found would be adding a JsonCreator (constructor in this case), that takes a Map.Entry<String, Map<String, String> and uses that to initialize the values, like this:
#JsonCreator
public Effect(Map.Entry<String, Map<String, String>> entry) {
this.type = entry.getKey();
this.parameters = entry.getValue();
}
If there's no way to get it done with a "normal" constructor, i will probably end up using this, as it uses Jackson's default mapping for Map.Entry, reducing possible error margin.
Add a static factory method that accepts a Map with a dynamic key:
#JsonCreator
public static Effect create(Map<String, Map<String, String>> map) {
String type = map.keySet().iterator().next();
return new Effect(type, map.get(type));
}
EDIT: Just noticed this is basically an uglier version of your own solution using Map.Entry. I would go with that instead.

Dozer Map DTO Mapping

I have a little "Object":
Map<Integer, Map<WeekDay, Map<String, Data>>> obj
and I want to Map it to:
Map<Integer, Map<WeekDay, Map<String, DataDto>>> returnObj
how can I achive this?
The way I wanted to use was this one:
map(schedule, Map<Integer.class, Map<WeekDay.class, Map<String.class, DataDto.class>>>);
but at the "Map" I am stuck, becuase I can't add a .class behind them and in this state it doesn't work...
I would suggest to simplify your Map if possible:
class A {
WeekDay weekDay;
String str;
Data obj;
}
Map<Integer, A> map = ...;
Iterables.transform(map.values(), new Function<Data, DataDto>() {
#Override
public Object apply(String input) {
return ...;
}
});
or you can put it inside your class:
class Dictionary {
Map<Integer, Map<WeekDay, Map<String, Data>>> obj;
getDataDto(Integer key, Weekday weekDay, String str) {
final Data data = obj.get(key).get(weekDay).get(str);
return (new Function<Data, DataDto>() {
...
}).apply(data);
}
}
Think about operations you are going to use over your data structure and come up with the proper class. Your nested map doesn't look okay.

Jackson, custom deserialization for specific field names

I would like to know if it is possible to customize the deserialization of json depending the field name, for example
{
id: "abc123",
field1: {...}
other: {
field1: {....}
}
}
I the previous json, I would like to have a custom deserializer for the fields named "field1", in any level in the json.
The reason: We have our data persisted as JSON, and we have a REST service that returns such data, but before return it, the service must inject extra information in the "field1" attribute.
The types are very dynamic, so we cannot define a Java class to map the json to use annotations.
An first approach was to deserialize to Map.class and then use JsonPath to search the $..field1 pattern, but this process is expensive for bigger objects.
I appreciate any help.
Thanks,
Edwin Miguel
You should consider registering a custom deserializer with the ObjectMapper for this purpose.
Then you should be able to simply map your JSON stream to a Map<String, Object> knowing that your field1 objects will be handled by your custom code.
I create a custom deserializer and added it to a SimpleModule for the ObjectMapper
public class CustomDeserializer extends StdDeserializer<Map> {
public CustomDeserializer() {
super(Map.class);
}
#Override
public Map deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
Map<String, Object> result = new HashMap<String, Object>();
jp.nextToken();
while (!JsonToken.END_OBJECT.equals(jp.getCurrentToken())) {
String key = jp.getText();
jp.nextToken();
if ("field1".equals(key)) {
MyObject fiedlObj= jp.readValueAs(MyObject.class);
//inject extra info
//...
result.put(key, fieldObj);
} else {
if (JsonToken.START_OBJECT.equals(jp.getCurrentToken())) {
result.put(key, deserialize(jp, ctxt));
} else if (JsonToken.START_ARRAY.equals(jp.getCurrentToken())) {
jp.nextToken();
List<Object> linkedList = new LinkedList<Object>();
while (!JsonToken.END_ARRAY.equals(jp.getCurrentToken())) {
linkedList.add(deserialize(jp, ctxt));
jp.nextToken();
}
result.put(key, linkedList);
} else {
result.put(key, jp.readValueAs(Object.class));
}
}
jp.nextToken();
}
return result;
}
}
The problem is that I had to implement the parsing for the remaining attributes.
For now, it is my solution...

How do I correctly deserialize .NET Dictionaries using Jackson?

to sum it up before the wall of text below :-)
I need help with how to deserialize a Dictionary using Jackson and a custom deserializer.
Right now I have an Android app communication with a .NET (C#) server. They use JSON to communicate.
On the JAVA-side, I am using Jackson to handle the JSON and on the .NET-side I am using the built in DataContractSerializer (I know, ppl will start commenting I should use something else, but Im not so... ;-) )
The problem is that I am sending Dictionaries from C# and I want that to be deserialized to HashMaps om the JAVA-side, but I havent found a good resource for how to do that.
One example of a Dictionary I am sending from C#:
// Here, the object Equipment is the key, and the int following indicates the amount
[DataMember]
public Dictionary<Equipment, int> EquipmentList { get; set; }
And just for reference, the Equipment object in C#:
[DataContract]
public class Equipment
{
[DataMember]
public uint Id { get; set; }
[DataMember]
public string Name { get; set; }
public override bool Equals(object obj)
{
if (obj.GetType() != this.GetType())
return false;
Equipment e = (Equipment)obj;
return e.Id == this.Id;
}
}
Its serialized correctly into decent JSON on the C#-side, the Dictionary looks like this:
//....
"EquipmentList":[
{
"Key":{
"EquipmentId":123,
"Name":"MyName"
},
"Value":1
}
//....
I have added a custom serializer (CustomMapSerializer), like this:
public static ObjectMapper mapper = new ObjectMapper();
private static SimpleDeserializers simpleDeserializers = new SimpleDeserializers();
private static StdDeserializerProvider sp = new StdDeserializerProvider();
public static void InitSerialization()
{
simpleDeserializers.addDeserializer(String.class, new CustomStringDeserializer());
simpleDeserializers.addDeserializer(Map.class, new CustomMapDeserializer());
sp.withAdditionalDeserializers(simpleDeserializers);
mapper.setDeserializerProvider(sp);
}
And decorated the field like this:
#JsonDeserialize(using=CustomMapDeserializer.class)
public Map<Equipment, Integer> EquipmentList;
And finally, when I run it I do get a break in the custom deserializer class, but I am not sure how to proceed from here:
public class CustomMapDeserializer extends JsonDeserializer<Map> {
#Override
public Map deserialize(JsonParser arg0, DeserializationContext arg1) throws IOException, JsonProcessingException
{
return new HashMap<Object, Object>(); // <-- I can break here
}
}
So, what I would like is some input on how to create a HashMap with the correct values in it, ie a deserialized Equipment as Key and an Int as value.
Anyone out there who can assist? =)
Ok, after a while testing and researching, this is what I came up with.
The custom deserializer looks like this:
public class CustomMapCoTravellerDeserializer extends JsonDeserializer<Map> {
#Override
public Map deserialize(JsonParser jp, DeserializationContext arg1) throws IOException, JsonProcessingException
{
HashMap<CoTravellers, Integer> myMap = new HashMap<CoTravellers, Integer>();
CoTravellers ct = new CoTravellers();
jp.nextToken(); // {
jp.nextToken(); // Key
jp.nextToken(); // {
jp.nextToken(); // "CoTravellerId"
jp.nextToken(); // CoTravellerId Id
int coTravellerValue = jp.getIntValue();
jp.nextToken(); // Name
jp.nextToken(); // Name Value
String coTravellerName = jp.getText();
jp.nextToken(); // }
jp.nextToken(); // "Value"
jp.nextToken(); // The value
int nbr = jp.getIntValue();
ct.CoTravellerId = coTravellerValue;
ct.Name = coTravellerName;
myMap.put(ct, nbr);
return myMap;
}
}
I think this will work, if I can only figure out the JsonMappingException I am getting... but I will post on that separately =)

Categories

Resources