'How to remove the empty objects and null values present in nested complex LinkedHashMap?
I have a complex nested LinkedHashMap for which I am assigning the values coming from another application. Some of the fields can have Null values since the values may be missing in input JSON.
I am looking for an approach to omit all the Null & Empty values present within my Complex/Nested LinkedHashMap. If it's a direct key, value LinkedHashMap then the process is quite straightforward forward but in my case, the LinkedHashMap value can have another LinkedHashmap within it.
Is there any simple, optimal Java 8 streams way to remove all empty/null values even within nested LinkedHashMap?
Following is the sample LinkedHashMap structure I have:
package io.template;
import java.util.LinkedHashMap;
public class TemplateNodeMap extends LinkedHashMap {
public TemplateNodeMap() {
put("type", null);
put("step", null);
put("error", new LinkedHashMap<>() {{
put("time", null);
put("errorId", new LinkedHashMap<>() {{
put("correctId", null);
}});
}});
put("sensor", new LinkedHashMap<>() {{
put("metadata", new LinkedHashMap<>() {{
put("time", null);
put("startTime", null);
}});
put("report", new LinkedHashMap<>() {{
put("type", null);
put("device", null);
put("deviceMetadata", null);
}});
}});
}
}
Currently, I have written a recursive code something like this:
public LinkedHashMap removeNull(LinkedHashMap map) {
LinkedHashMap templateNodeMap = new LinkedHashMap();
map.forEach((key, value) -> {
if (value != null && value instanceof LinkedHashMap) {
templateNodeMap.put(key, removeNull((LinkedHashMap) value));
} else if (value != null) {
templateNodeMap.put(key, value);
}
});
return templateNodeMap;
}
The above code is working for null but it does not remove the empty values. For some fields, I am getting the value as sensor={metadata={},report={}}}, I would like to remove fields altogether if it does not have any values. I do not wish to get even the empty object {}.
Can someone please let me know if there is a way to do it in Java Streams or in a recursive approach. Thanks in advance.
Solution 1:[1]
You can declare a variable to store the results of your removeNull() method inside the if statement that is making a recursive call to the method. You then check if the returned LinkedHashMap is not empty before adding it to the result LinkedHashMap like:
public LinkedHashMap removeNull(LinkedHashMap map) {
LinkedHashMap templateNodeMap = new LinkedHashMap();
map.forEach((key, value) -> {
if (value != null && value instanceof LinkedHashMap) {
LinkdedHashMap hashMap = removeNull((LinkedHashMap) value);
if(!hashMap.isEmpty()) {
templateNodeMap.put(key, hashMap);
}
} else if (value != null) {
templateNodeMap.put(key, value);
}
});
return templateNodeMap;
}
Solution 2:[2]
A simple void method:
public static void removeNull(Map<?, ?> map) {
map.values().forEach(e -> {
if (e instanceof Map)
removeNull((Map<?, ?>) e);
});
map.values().removeIf(e -> e == null || e instanceof Map && ((Map<?, ?>) e).isEmpty());
}
And call it with removeNull(test);
Solution 3:[3]
Based on your attempt , I can suggest some addition :
map.forEach((key, value) -> {
if (value != null && value instanceof LinkedHashMap) {
templateNodeMap.put(key, removeNull((LinkedHashMap) value));
templateNodeMap.compute(key, (x,y)->(y.isEmpty()) ? null : y);
} else if (value != null) {
templateNodeMap.put(key, value);
}
});
Once you returned from recursion inside if block , you can compute for empty values of mapping & remove if it contains empty values.
Solution 4:[4]
You can do this with the let .. in syntax like so:
let f () = let a = 1 in let b = 2 in a + b
f () // gives 3 as a result
But I would really recommend against doing multiple single-line definitions like this. It's hard for people to read.
Solution 5:[5]
As explained by Phillip, the let .. in .. construct allows you to define a local variable as part of a one-line expression.
However, your example seems to be trying to define multiple top-level definitions in a module, which is something you cannot achieve with let .. in ...
As far as I can tell, you can actually do this by separating the definitions with two semicolons, i.e. ;;. If I save the following as test.fs and load it using #load, I get no errors:
module Digits
type Digit = Unison | Semitone | Tone | MinorThird | MajorThird | PerfectFourth | AugmentedFourth | PerfectFifth | MinorSixth | MajorSixth | MinorSeventh | MajorSeventh type 'd GeneralizedDigit = SmallDigit of 'd | Octave type 't Number = EmptyNumber | CountedNumber of 't * 't Number
let swapOctave: Digit GeneralizedDigit -> Digit GeneralizedDigit = fun x -> match x with SmallDigit Unison -> Octave | Octave -> SmallDigit Unison | g -> g;; let limitLength: 'r Number -> Digit = fun a -> match a with EmptyNumber -> Unison | CountedNumber(_,EmptyNumber) -> Semitone | CountedNumber(_,CountedNumber(_,EmptyNumber)) -> Tone | CountedNumber(_,CountedNumber(_,CountedNumber(_,EmptyNumber))) -> MinorThird | _ -> MajorSeventh
I tested this in F# 5.0. It may be the case that this has changed in F# 6 which removed deprecated features like #light "off". The removal of ;; is not discussed in the post, but it may have been a related change. If that is the case, you may report it as a regression - but it is likely support for ;; should also be removed!
As mentioned by Phillip, I do not see any reason for actually trying to do this.
Solution 6:[6]
If you want multiple let bindings to bound values to variables, then you also can use the "tuple syntax".
let x,y,z = 1, "Hi", 3.0
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | ktxdev |
| Solution 2 | |
| Solution 3 | |
| Solution 4 | Phillip Carter |
| Solution 5 | Tomas Petricek |
| Solution 6 | David Raab |
