Extract and copy values from JSONObject to HashMap - java

I am having a JSON which contains upto 1000 Keys. I need some specific keys out of it.
Rather than traversing through the JSON and finding key and put its value in required parameter.
I thought of doing it in other way.
I am creating a HashMap of the keys I need.
Now i want to pass a JSONObject through it, where if we find the keys in JSONObject, it will automatically update the HashMap with the required keys.
Is there some function given by Spring where we can do it easily or do I Have to loop through it.
For Example:
JSONObject:-
{
"a":"a",
"b":"b",
"c":"c",
"d":"d",
"e":"e",
}
HashMap that I created :-
Map<String, Object> keys = new HashMap<>();
keys .put("a", "");
keys .put("b", "");
I want a function where i would pass two params
function HashMap mapJsonToHashMap(HashMap, JSONObject) {
}
Returned HashMap would be :-
{
"a":"a",
"b":"b"
}

IMO 1000 keys are not a big deal, so I would go for a simple solution of deserialize it to an object, then just filter/map using streams. Something like:
ObjectMapper objectMapper = new ObjectMapper();
Set<String> keys = new HashSet<>();
keys.add("key-1");
keys.add("key-3");
List<Parameter> list =
objectMapper.readValue("[{ \"key\":\"key-1\", \"value\":\"aaa\" }, { \"key\":\"key-2\", \"value\":\"bbb\" }, { \"key\":\"key-3\", \"value\":\"ccc\" }]", new TypeReference<List<Parameter>>(){});
List<Parameter> filteredList = list.stream()
.filter(l -> keys.contains(l.getKey()))
.collect(Collectors.toList());
// in case you really want to put results in a Map
Map<String, String> KeyValueMap = filteredList.stream()
.collect(Collectors.toMap(Parameter::getKey, Parameter::getValue));
public class Parameter
{
private String key;
private String value;
public String getKey()
{
return key;
}
public void setKey(String key)
{
this.key = key;
}
public String getValue()
{
return value;
}
public void setValue(String value)
{
this.value = value;
}
}

You can try something like this,
Convert JSON to HashMap
Then remove unwanted entries from the converted map
public HashMap mapJsonToHashMap(HashMap keys, JSONObject json) {
// Convert JSON to HashMap
ObjectMapper mapper = new ObjectMapper();
Map<String, String> jsonMap = mapper.readValue(json, Map.class);
// Iterate jsonMap and remove invalid keys
for(Iterator<Map.Entry<String, String>> it = jsonMap .entrySet().iterator();
it.hasNext(); ) {
Map.Entry<String, String> entry = it.next();
if(!keys.containsKey(entry.getKey())) {
it.remove();
}
}
return jsonMap;
}

Related

How can I transform a Map with a value of Object to the appropriate type?

I am working on a project where I need to accept a Map called properties that is of type Map<String, Object>. There are going to be potentially many different keys in this Map, but I only care about one: xpath. An xpath can have one of three different types of values:
A string, such as {"xpath": "path/to/xml/tag"}
A List of xpaths, such as: {"xpath": ["path/to/xml/tag1", "tag2", "path/tag3"}
A Map<String, Map<String, Boolean>>, such as:
{
"xpath":
{
"path/to/xml":
{
"setting1?": true,
"setting2?": true
},
"path/tag2":
{
"setting1?": false,
"setting2": true
},
"path/to/tag3": null
}
}
Now I have three variables: String xpath, Set<String> xpaths, Map<String, Map<String, boolean> xpathMap. I have a function that is supposed to try and map the values of the "xpath" key in the properties map, and it looks like this:
private void decideXPathType(Map<String, Object> properties)
{
Object propertiesXPath = properties.get("xpath");
if (propertiesXPath instanceof String)
{
this.xpath = (String) propertiesXPath;
} else if (propertiesXPath instanceof List)
{
this.xpaths = new HashSet<String>((List) propertiesXPath);
} else if (propertiesXPath instanceof Map)
{
for (Object key : ((Map) propertiesXPath).keySet())
{
Map<String, Boolean> value = (Map<String, Boolean>) ((Map) propertiesXPath).get(key);
this.xpathMap.put((String) key, value);
}
} else
{
throw new IllegalArgumentException("the xpath value is neither String, List, or Map<String, Boolean>");
}
}
But this function looks so bad - there is so much casting, etc - and although it works, it just looks too messy, and I imagine something can go wrong... any ideas on how I can make this cleaner?
Edit: Some more details
The properties map is originally a json JsonNode requestBody that I receive from a service. Using ObjectMapper, I create a properties map as such:
Map<String, Object> properties = new ObjectMapper().convertValue(new ObjectMapper().readTree(requestBody), new TypeReference<Map<String, Object>>(){});
If I receive a json string that is the value of the xpathMap example that I gave, I get something that looks like this:
Hope this information helps?
In your JSON, use different keys for these different types of values: String, List and Map. Deserializing a map:
#Test
public void test0() throws IOException {
ObjectMapper om = new ObjectMapper();
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("xpath-object.json");
JsonNode jsonNode = om.readTree(inputStream);
Map<String, Map<String, Boolean>> value = om.readValue(jsonNode.get("xpath").toString(), Map.class);
// prints {path/to/xml={setting1?=true, setting2?=true}, path/to/tag3=null, path/tag2={setting1?=false, setting2=true}}
System.out.println(value);
}
If you need to work with 3rd party JSON, you can use following approach:
#Test
public void test() throws IOException {
testThemAll("xpath-scalar.json");
testThemAll("xpath-array.json");
testThemAll("xpath-object.json");
// prints:
// path/to/xml/tag
// [path/to/xml/tag1, tag2, path/tag3]
// {path/to/xml={setting1?=true, setting2?=true}, path/to/tag3=null, path/tag2={setting1?=false, setting2=true}}
}
private void testThemAll(String fileName) throws IOException {
ObjectMapper om = new ObjectMapper();
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(fileName);
JsonNode jsonNode = om.readTree(inputStream).get("xpath");
if (jsonNode.isValueNode())
System.out.println(jsonNode.asText());
else if (jsonNode.isArray()) {
System.out.println(om.readValue(jsonNode.toString(), List.class));
} else if (jsonNode.isObject()) {
Map<String, Map<String, Boolean>> value = om.readValue(jsonNode.toString(), Map.class);
System.out.println(value);
}
}

Update the JSON element to NULL if it has a specific value in it

I have a JSON which looks like,
{
"person": {
"name":"Sam",
"surname":"ngonma"
},
"car": {
"make":"toyota",
"model":"yaris"
}
}
I am writing this to Amazon SQS with the below lines,
ObjectMapper mObjectMapper = new ObjectMapper();
sqsExtended.sendMessage(new SendMessageRequest(awsSQSUrl, mObjectMapper.writeValueAsString(claim)));
I have a separate array of values, if the JSON has its value in that array I have to write the field as null.
If my string array is ["Sam", "Toyota"] my final JSON should look like this,
{
"person": {
"name":null,
"surname":"ngonma"
},
"car": {
"make":null,
"model":"yaris"
}
}
The string array is externalized. It may have additional values in future too. Could someone suggest me a good link or idea to address this ?
The most flexible way I could come up with is to use Jackson's JsonAnyGetter annotation. It allows you to provide Jackson with a Map representation of the state of your pojo. filtering values from a Map can be done in iterative way. filtering values from a Map that contains Maps can be done in recursive way.
Here is a solution I built from provided question
public class Claim {
Map<String, Object> properties = new HashMap<>();
public Claim() {
// may be populated from instance variables
Map<String, String> person = new HashMap<>();
person.put("name", "Sam");
person.put("surname", "ngonma");
properties.put("person", person);
Map<String, String> car = new HashMap<>();
car.put("make", "Toyota");
car.put("model", "yaris");
properties.put("car", car);
}
// nullify map values based on provided array
public void filterProperties (String[] nullifyValues) {
filterProperties(properties, nullifyValues);
}
// nullify map values of provided map based on provided array
#SuppressWarnings("unchecked")
private void filterProperties (Map<String, Object> properties, String[] nullifyValues) {
// iterate all String-typed values
// if value found in array arg, nullify it
// (we iterate on keys so that we can put a new value)
properties.keySet().stream()
.filter(key -> properties.get(key) instanceof String)
.filter(key -> Arrays.asList(nullifyValues).contains(properties.get(key)))
.forEach(key -> properties.put(key, null));
// iterate all Map-typed values
// call this method on value
properties.values().stream()
.filter(value -> value instanceof Map)
.forEach(value -> filterProperties((Map<String, Object>)value, nullifyValues));
}
// provide jackson with Map of all properties
#JsonAnyGetter
public Map<String, Object> getProperties() {
return properties;
}
}
test method
public static void main(String[] args) {
try {
ObjectMapper mapper = new ObjectMapper();
Claim claim = new Claim();
claim.filterProperties(new String[]{"Sam", "Toyota"});
System.out.println(mapper.writeValueAsString(claim));
} catch (Exception e) {
e.printStackTrace();
}
}
output
{"car":{"model":"yaris","make":null},"person":{"surname":"ngonma","name":null}}

Filter pojo properties by some pattern

I've some server response (a long one) which I've converted to POJO (by using moshi library).
Eventually I have list of "Items" , each "Item" looks like follow :
public class Item
{
private String aa;
private String b;
private String abc;
private String ad;
private String dd;
private String qw;
private String arew;
private String tt;
private String asd;
private String aut;
private String id;
...
}
What I actually need, is to pull all properties which start with "a" , and then I need to use their values for further req ...
Any way to achieve it without Reflection ? (usage of streams maybe ?)
Thanks
With guava-functions tranformation you might transform your items with somethng following:
public static void main(String[] args) {
List<Item> items //
Function<Item, Map<String, Object>> transformer = new Function<Item, Map<String, Object>>() {
#Override
public Map<String, Object> apply(Item input) {
Map<String, Object> result = new HashMap<String, Object>();
for (Field f : input.getClass().getDeclaredFields()) {
if(! f.getName().startsWith("a")) {
continue;
}
Object value = null;
try {
value = f.get(input);
} catch (IllegalAccessException e) {
throw new RuntimeException("failed to cast" + e)
}
result.put(f.getName(), value);
}
return result
};
Collection<Map<String, Object> result
= Collections2.transform(items, transformer);
}
Sounds like you may want to perform your filtering on a regular Java map structure.
// Dependencies.
Moshi moshi = new Moshi.Builder().build();
JsonAdapter<Map<String, String>> itemAdapter =
moshi.adapter(Types.newParameterizedType(Map.class, String.class, String.class));
String json = "{\"aa\":\"value1\",\"b\":\"value2\",\"abc\":\"value3\"}";
// Usage.
Map<String, String> value = itemAdapter.fromJson(json);
Map<String, String> filtered = value.entrySet().stream().filter(
stringStringEntry -> stringStringEntry.getKey().charAt(0) == 'a')
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
You could wrap up the filtering logic in a custom JsonAdapter, but validation and business logic tends to be nice to leave to the application usage layer.

How to convert Java class to Map<String, String> and convert non-string members to json using jackson?

I have some class in Java that I want to convert to a Map<String, String>. The catch is that any fields of my java class that don't have an obvious String representation (collections, other classes) should be converted to json strings.
Here's an example:
#Data
#AllArgsConstructor
class MyClass {
String field1;
Long field2;
Set<String> field3;
OtherClass field4;
}
#Data
#AllArgsConstructor
class OtherClass {
String field1;
String field2;
}
ObjectMapper mapper = new ObjectMapper();
MyClass myClass = new MyClass("value",
123L,
Sets.newHashSet("item1", "item2"),
new OtherClass("value1", "value2"));
Map<String, String> converted =
mapper.convertValue(myClass, new TypeReference<Map<String, String>>(){});
At this point, converted should look like the following:
"field1" -> "value"
"field2" -> "123"
"field3" -> "[\"item1\", \"item2\"]"
"field4" -> "{\"field1\":\"value1\",\"field2\":\"value2\"}"
Instead, the call to mapper.convertValue fails when trying to deserizlize the Set with the exception java.lang.IllegalArgumentException: Can not deserialize instance of java.lang.String out of START_ARRAY token.
Are there any special configurations I can annotate MyClass with or ways to configure the ObjectMapper to make this work the way I want it to?
Here's one way to do it.
private static final ObjectMapper mapper = new ObjectMapper();
public Map<String, String> toMap(Object obj) {
// Convert the object to an intermediate form (map of strings to JSON nodes)
Map<String, JsonNode> intermediateMap = mapper.convertValue(obj, new TypeReference<Map<String, JsonNode>>() {});
// Convert the json nodes to strings
Map<String, String> finalMap = new HashMap<>(intermediateMap.size() + 1); // Start out big enough to prevent resizing
for (Map.Entry<String, JsonNode> e : intermediateMap.entrySet()) {
String key = e.getKey();
JsonNode val = e.getValue();
// Get the text value of textual nodes, and convert non-textual nodes to JSON strings
String stringVal = val.isTextual() ? val.textValue() : val.toString();
finalMap.put(key, stringVal);
}
return finalMap;
}
And if you want to convert the Map<String, String> back to the original class...
public static <T> T fromMap(Map<String, String> map, Class<T> clazz) throws IOException {
// Convert the data to a map of strings to JSON nodes
Map<String, JsonNode> intermediateMap = new HashMap<>(map.size() + 1); // Start out big enough to prevent resizing
for (Map.Entry<String, String> e : map.entrySet()) {
String key = e.getKey();
String val = e.getValue();
// Convert the value to the right type of JsonNode
JsonNode jsonVal;
if (val.startsWith("{") || val.startsWith("[") || "null".equals(val)) {
jsonVal = mapper.readValue(val, JsonNode.class);
} else {
jsonVal = mapper.convertValue(val, JsonNode.class);
}
intermediateMap.put(key, jsonVal);
}
// Convert the intermediate map to an object
T result = mapper.convertValue(intermediateMap, clazz);
return result;
}

How to properly lazy initialize Map of Map of Map?

It may be a bad practice, but I haven't been able to figure out any better solution for my problem. So I have this map
// Map<state, Map<transition, Map<property, value>>>
private Map<String, Map<String, Map<String, String>>> properties;
and I want to initialize it so I don't get NullPointerException with this
properties.get("a").get("b").get("c");
I tried this one but I didn't work (obviously)
properties = new HashMap<String, Map<String, Map<String,String>>>();
Other things I tried didn't compile.
Also if you have any ideas how to avoid this nested maps, I would appreciate it.
It seems to me that you need to create your own Key class:
public class Key {
private final String a;
private final String b;
private final String c;
public Key(String a, String b, String c) {
// initialize all fields here
}
// you need to implement equals and hashcode. Eclipse and IntelliJ can do that for you
}
If you implement your own key class, your map will look like this:
Map<Key, String> map = new HashMap<Key, String>();
And when looking for something in the map you can use:
map.get(new Key("a", "b", "c"));
The method above will not throw a NullPointerException.
Please remember that for this solution to work, you need to override equals and hashcode in the Key class. There is help here. If you don't override equals and hashcode, then a new key with the same elements won't match an existing key in the map.
There are other possible solutions but implementing your own key is a pretty clean one in my opinion. If you don't want to use the constructor you can initialize your key with a static method and use something like:
Key.build(a, b, c)
It is up to you.
You need to put maps in your maps in your map. Literally:
properties = new HashMap<String, Map<String, Map<String,String>>>();
properties.put("a", new HashMap<String, Map<String,String>>());
properites.get("a").put("b", new HashMap<String,String>());
If your target is lazy initialization without NPE you have to create your own map:
private static abstract class MyMap<K, V> extends HashMap<K, V> {
#Override
public V get(Object key) {
V val = super.get(key);
if (val == null && key instanceof K) {
put((K)key, val = create());
}
return val;
}
protected abstract V create();
}
public void initialize() {
properties = new MyMap<String, Map<String, Map<String, String>>>() {
#Override
protected Map<String, Map<String, String>> create() {
return new MyMap<String, Map<String, String>>() {
#Override
protected Map<String, String> create() {
return new HashMap<String, String>();
}
};
}
};
}
You could use a utility method:
public static <T> T get(Map<?, ?> properties, Object... keys) {
Map<?, ?> nestedMap = properties;
for (int i = 0; i < keys.length; i++) {
if (i == keys.length - 1) {
#SuppressWarnings("unchecked")
T value = (T) nestedMap.get(keys[i]);
return value;
} else {
nestedMap = (Map<?, ?>) nestedMap.get(keys[i]);
if(nestedMap == null) {
return null;
}
}
}
return null;
}
This can be invoked like this:
String result = get(properties, "a", "b", "c");
Note that care is required when using this as it is not type-safe.
The only way to do it with this structure is to pre-initialise the 1st and 2nd level maps with ALL possible keys. If this is not possible to do you can't achieve what you are asking with plain Maps.
As an alternative you can build a custom data structure that is more forgiving. For example a common trick is for a failed key lookup to return an "empty" structure rather than null, allowing nested access.
You can't initialize this in one go, since you normally don't know what keys you'll have in advance.
Thus you'd have to check whether the submap for a key is null and if so you might add an empty map for that. Preferably you'd only do that when adding entries to the map and upon retrieving entries you return null if one of the submaps in the path doesn't exist. You could wrap that in your own map implementation for ease of use.
As an alternative, apache commons collections' MultiKeyMap might provide what you want.
It's impossible to use properties.get("a").get("b").get("c"); and be sure to avoid null unless you make your own Map. In fact, you can't predict that your map will contains "b" key.
So try to make your own class to handle nested get.
I think a better solution is using an object as the only key to the map of values. The key will be composed of three fields, state, transition and property.
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
public class Key {
private String state;
private String transition;
private String property;
public Key(String state, String transition, String property) {
this.state = state;
this.transition = transition;
this.property = property;
}
#Override
public boolean equals(Object other) {
return EqualsBuilder.reflectionEquals(this, other);
}
#Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
}
When you check for a value, the map will return null for a key that is not associated with a value
Map<Key, String> values = new HashMap<Key, String>();
assert values.get(new Key("a", "b", "c")) == null;
values.put(new Key("a", "b", "c"), "value");
assert values.get(new Key("a", "b", "c")) != null;
assert values.get(new Key("a", "b", "c")).equals("value");
To efficiently and correctly use an object as a key in a Map you should override the methods equals() and hashCode(). I have built thos methods using the reflective functionalities of the Commons Lang library.
I think, following is the easier way:
public static final Map<Integer, Map<Integer, Map<Integer, Double>>> A_Map = new HashMap<Integer, Map<Integer, Map<Integer, Double>>>()
{
{
put(0, new HashMap<Integer, Map<Integer, Double>>()
{
{
put(0, new HashMap<Integer, Double>()
{
{
put(0, 1 / 60.0);
put(1, 1 / 3600.0);
}
});
put(1, new HashMap<Integer, Double>()
{
{
put(0, 1 / 160.0);
put(1, 1 / 13600.0);
}
});
}
});
put(1, new HashMap<Integer, Map<Integer, Double>>()
{
{
put(0, new HashMap<Integer, Double>()
{
{
put(0, 1 / 260.0);
put(1, 1 / 3600.0);
}
});
put(1, new HashMap<Integer, Double>()
{
{
put(0, 1 / 560.0);
put(1, 1 / 1300.0);
}
});
}
});
}
};
Using computeIfAbsent/putIfAbsent makes it simple:
private <T> void addValueToMap(String keyA, String keyB, String keyC, String value) {
map.computeIfAbsent(keyA, k -> new HashMap<>())
.computeIfAbsent(keyB, k -> new HashMap<>())
.putIfAbsent(keyC, value);
}

Categories