'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