Pablo Pablo - 3 months ago 13
Java Question

Functional solution to reconstruct Map from String

I have been looking around but couldn't come up with an elegant functional solution using Java 8. This is the problem I want to solve:

I have 'serialized' maps into a single string, for example

{ type -> 'fruit',
color -> 'yellow',
age -> 5 }


would become:

type:fruit;color:yellow;age:5


Now, I want to recreate the original Map from the string

Arrays.stream(input.split(";"))
.map(v -> v.split(":"))
.collect(Collectors.toMap(c -> c[0], c -> c.[1]);


Notice the code above would result in a
NullPointerException
if there is no ':' within the list, this can be solved with:

c.length > 1 ? c[1] : c[0]


But this doesn't feel right. Any suggestions or alternatives using Java8 APIs?

Answer

This works for me:

class StreamToInflateStringToMap {
    private static String flatString = "type:fruit;color:yellow;age:5";
    private static Function<String, String> keyMapper =
            s -> s.substring(0, s.indexOf(":"));
    private static Function<String, String> valueMapper =
            s -> s.substring(s.indexOf(":") + 1);

    public static Map<String, String> inflateStringToMap(String flatString) {
        Map<String, String> inflatedMap = Stream.of(flatString.split(";")).
                collect(Collectors.toMap(keyMapper, valueMapper));
        return inflatedMap;
    }

    public static void main(String[] args) {
        System.out.println("Flat String:\n" + flatString);
        Map<String, String> inflatedMap = inflateStringToMap(flatString);
        System.out.println("Inflated Map:\n" + inflatedMap);
    }
}

Note that I've assumed that you meant that you wanted a solution for when the semicolon was missing (that is: there is only one item in the Map). If there is no colon then it is entirely right that an exception be thrown, because it means that there is no mapping defined in the string and a bad value must have been passed to the method.

The comment put forward by Alexey Romanov is also valid: can you guarantee that the delimiter characters will not be found within the content as part of the actual key/value Strings? If not then you'll hit trouble (whatever method of inflating you use) so it's probably necessary to validate the values being put into the original copy of the Map.

Also note that I've declared the Function<String, String> mappers as static members of the class, but you could shoehorn them directly into the Stream flow if you never need to use them anywhere else and you don't think it makes the code too ugly.

Additionally, some hunting through the Java 8 API has uncovered an alternative way to create a Stream from a delimiter-separated String. You can use the Pattern.splitAsStream(CharSequence) method, something like this:

private static final Pattern SINGLE_SEMICOLON = Pattern.compile(";");

public static Map<String, String> inflateStringToMap(String flatString) {
    Map<String, String> inflatedMap =
            SINGLE_SEMICOLON.splitAsStream(flatString).
            collect(Collectors.toMap(keyMapper, valueMapper));
    return inflatedMap;
}

(Note the warning in the API about feeding mutable CharSequence types (such as StringBuilder) to the splitAsStream method.)

Comments