Home > Back-end >  Converting delimited string/text into a map object
Converting delimited string/text into a map object

Time:10-15

I have been trying to figure out a solution for a while now, but I can't seem to get any suitable answer after thinking through/surfing the net for a solution. Hope the community can help me out!

I have some string and wishes to convert them into a nested map object, example below.

Fruits.Apple.Red
Fruits.Apple.Green
Fruits.Orange.Yellow
Fruits.Watermelon.Yellow
Fruits.Watermelon.Red

I would like to convert the above example into something like this.

{ Fruits:{
       Apple:{
              Red: null,
              Green: null
             },
       Orange:{
              Yellow: null
              },
   Watermelon:{enter code here
              Yellow: null,
              Red: null
              }
       }
}

Pardon me if you find this example to be a bad one. There is a reason why the value for the last child is null, I am trying to reproduce the problem I am facing.

CodePudding user response:

  1. Convert strings to map
  • Split strings to arrays (String.split(".") function)
  • Convert arrays to Map<String,Map<String,Object>:
arrays: ["Fruits","Apple","Red"], ["Fruits","Apple","Green"], ... -> 
map: {"Fruits":{"Apple":{"Red":null},{"Green":null}},"Orange":{...},...}} 
  1. Convert map to json using Gson (https://github.com/google/gson) or Jackson
    Gson gson = new Gson();
    return gson.toJson(map);

CodePudding user response:

A recursive function addToMap may be implemented to find the key before the dot . as a delimiter, creating a map if necessary using Map::computeIfAbsent, or adding the key with null value to the top-level map:

@SuppressWarnings({"rawtypes", "unchecked"})
static void addToMap(String s, Map<String, Map> upperLevel) {
    int dotPos = s.indexOf(".");
    if (dotPos < 0) {
        upperLevel.put(s, null);
    } else {
        String key = s.substring(0, dotPos);
        addToMap(s.substring(dotPos   1), upperLevel.computeIfAbsent(key, k -> new HashMap<>()));
    }
}

Then the test may look as follows:

String[] arr = {
        "Fruits.Cherry",
        "Fruits.Apple.Red",
        "Fruits.Apple.Green",
        "Fruits.Orange.Yellow",
        "Fruits.Watermelon.Yellow",
        "Fruits.Watermelon.Red",
};

Map<String, Map> result = new HashMap<>();
for (String t : arr) {
    addToMap(t, result);
}
System.out.println(result);

Output:

{Fruits={Apple={Red=null, Green=null}, Cherry=null, Watermelon={Red=null, Yellow=null}, Orange={Yellow=null}}}

If an insertion order is important, LinkedHashMap should be created instead of HashMap or an overridden version with Supplier<Map> may be used:

Supplier<Map> mapSupplier = LinkedHashMap::new;
Map<String, Map> result = mapSupplier.get();
for (String t : arr) {
    addToMap(t, result, mapSupplier);
}
System.out.println(result);

@SuppressWarnings({"rawtypes", "unchecked"})
static void addToMap(String s, Map<String, Map> upperLevel, Supplier<Map> mapSupplier) {
    int dotPos = s.indexOf(".");
    if (dotPos < 0) {
        upperLevel.put(s, null);
    } else {
        String key = s.substring(0, dotPos);
        addToMap(s.substring(dotPos   1), upperLevel.computeIfAbsent(key, k -> mapSupplier.get()), mapSupplier);
    }
}

Output (order changed):

{Fruits={Cherry=null, Apple={Red=null, Green=null}, Orange={Yellow=null}, Watermelon={Yellow=null, Red=null}}}
  • Related