Writing a Function for parsing data – Functional style programming – extending API

183. Writing a Function<String, T> for parsing data

Let’s assume that we have the following text:

String text = “””
  test, a, 1, 4, 5, 0xf5, 0x5, 4.5d, 6, 5.6, 50000, 345,
  4.0f, 6$3, 2$1.1, 5.5, 6.7, 8, a11, 3e+1, -11199, 55                    
  “””;

And, the goal is to find a solution that extracts from this text only the numbers. Depending on a given scenario, we may need only the integers, or only the doubles, and so on. Sometimes, we may need to perform some text replacements before extraction (for instance, we may want to replace the xf characters with a dot, 0xf5 = 0.5).A possible solution to this problem is to write a method (let’s name it parseText()) that takes as argument a Function<String, T>. The Function<String, T> gives us the flexibility to shape any of the following:

List<Integer> integerValues
  = parseText(text, Integer::valueOf);
List<Double> doubleValues
  = parseText(text, Double::valueOf);

List<Double> moreDoubleValues
  = parseText(text, t -> Double.valueOf(t.replaceAll(
      “\\$”, “”).replaceAll(“xf”, “.”).replaceAll(“x”, “.”)));

The parseText() should perform several steps until it reaches the final result. Its signature can be as follows:

public static <T> List<T> parseText(
    String text, Function<String, T> func) {
  …
}

First, we have to split the received text by the comma delimiter and extract the items in a String[]. This way we have access to each item from the text.Second, we can stream the String[] and filter any empty items.Third, we can call the Function.apply() to apply the given function to each item (for instance, to apply Double::valueOf). This can be done via the intermediate operation map(). Since some items may be invalid numbers, we have to catch and ignore any Exception (it is a bad practice to swallow an exception like this but, in this case, there is really nothing else to do). For any invalid number, we simply return null.Fourth, we filter all null values. This means that the remaining stream contains only numbers that passed through Function.apply().Fifth, we collect the stream in a List and return it.Gluing these 5 steps will result in the following code:

public static <T> List<T> parseText(
    String text, Function<String, T> func) {
  return Arrays.stream(text.split(“,”)) // step 1 and 2
    .filter(s -> !s.isEmpty())
    .map(s -> {
       try {
         return func.apply(s.trim());   // step 3
       } catch (Exception e) {}
       return null;
    })
    .filter(Objects::nonNull)           // step 4
    .collect(Collectors.toList());      // step 5
}

Done! You can use this example to solve a wide range of similar problems.