In Java 8, I want to convert a JSON string to a map, and apply "complex" transformations to the keys. As an example, that "complex" transformation will simply be a lower case transformation.
Values in the input JSON could be strings or nested JSON objects.
My code is actually working, but I struggle to fix an unchecked cast warning.
Example JSON input (String) :
{
"Key1": "value1",
"Key2": {
"Key2.1": "value2.1"
}
}
Desired output (Map) :
"key1" -> "value1"
"key2" ->
"key2.1" -> "value2.1"
For that part, I used Jackson (2.9.8) and defined the following function :
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
Map<String, Object> convertJsonStringToMap(String json) throws IOException {
ObjectMapper mapper = new ObjectMapper();
TypeReference type = new TypeReference<Map<String, Object>>(){};
return mapper.readValue(json, type);
}
Since values can be strings or JSON objects, the return type of that function is Map<String, Object>. Note also that
readValue (in ObjectMapper class) uses generics, its signature is :
<T> T readValue(String content, TypeReference valueTypeRef)
I defined the following function :
Map<String, Object> transformKeys(Map<String, Object> map) {
Map<String, Object> result = new HashMap<>(map.size());
for (Map.Entry<String, Object> entry : map.entrySet()) {
Object value = entry.getValue();
if (value instanceof Map) {
value = transformKeys((Map<String, Object>) value);
}
// toLowerCase() is the transformation here (example), but I could have used something else
result.put(entry.getKey().toLowerCase(), value);
}
return result;
}
To handle nested maps, this function is recursive. But since it takes a Map<String, Object> as parameter,
I must cast value to Map<String, Object> to call the method recursively.
String json = "{\"Key1\": \"value1\", \"Key2\": { \"Key2.1\": \"value2.1\" }}";
Map<String, Object> initialMap = convertJsonStringToMap(json);
Map transformedMap = transformKeys(initialMap);
System.out.println(transformedMap);
This code works, and prints, as expected :
{key1=value1, key2={key2.1=value2.1}}
But this line in transformKeys function :
value = transformKeys((Map<String, Object>) value);
produces a warning :
[WARNING] App.java:[29,74] unchecked cast
required: java.util.Map<java.lang.String,java.lang.Object>
found: java.lang.Object
The warning is clear and I understand it (the compiler cannot know if value is really an instance of Map<String, Object>), but is there a way to get rid of it?
(No @SuppressWarnings please, nor -Xlint:none) :D
I feel like returning a Map<String, Object> from convertJsonStringToMap is
not the cleanest way to convert JSON String to a Map, but I can't find an other way to do it with Jackson.
You need to use Object as Map value because it could be another Map, List or primitive (String, Integer, etc.). Jackson allows also to manipulate JSON using JsonNode types. We need to traverse JSON object but also JSON array (you forgot about it). In that case we need to:
JSON to JsonNode.JsonNode API.MapSimple implementation:
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class JsonApp {
public static void main(String[] args) throws Exception {
String json = "{\"Key1\": \"value1\", \"Key2\": { \"Key2.1\": \"value2.1\" }, \"Key3\":[{\"pRiMe\":11}]}";
Map<String, Object> map = convertJsonStringToMap(json);
System.out.println(map);
}
private static Map<String, Object> convertJsonStringToMap(String json) throws IOException {
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(json);
transformKeys(node);
TypeReference mapType = new TypeReference<Map<String, Object>>() {
};
return mapper.convertValue(node, mapType);
}
private static void transformKeys(JsonNode parent) {
if (parent.isObject()) {
ObjectNode node = (ObjectNode) parent;
List<String> names = new ArrayList<>();
node.fieldNames().forEachRemaining(names::add);
names.forEach(name -> {
JsonNode item = node.remove(name);
transformKeys(item);
node.replace(name.toLowerCase(), item);
});
} else if (parent.isArray()) {
ArrayNode array = (ArrayNode) parent;
array.elements().forEachRemaining(JsonApp::transformKeys);
}
}
}
Above code prints:
{key1=value1, key2={key2.1=value2.1}, key3=[{prime=11}]}
We got rid of unsafe casting and our implementation is more concise.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With