3

I need to have configurable timeout for a project, which reads different configs from YAML file.

I noticed that java.time.Duration has method parse which is used by Jackson to deserialize duration strings. The problem is that it uses ISO-8601 formatting and expects the duration to have PnDTnHnMn.nS format. While it is a good idea to follow standards, asking people provide timeout as PT10M is not the best option and 10m is preferred.

I did write a custom deserializer for duration fields, but seems strange that Jackson can't handle this by default.

What is the easiest way to deserialize human friendly 10m, 5s and 1h to java.time.Duration using Jackson ObjectMapper?

DaTval
  • 316
  • 1
  • 6
  • 14
  • 2
    *FYI:* Reading and interpreting input like `10m` is called *parsing*, not deserializing. – Andreas Sep 07 '18 at 22:14
  • 1
    It looks like these inputs are non-standard, for example, how do I know that `m` means minutes and not months? I'd expect that sort of thing to be optionally available by the same library that generates these human friendly strings. – Ben Barkay Sep 07 '18 at 22:23
  • 1
    I don't think Duration even have a month option – DaTval Sep 07 '18 at 22:47
  • I think just putting `timeout-minutes` in configs is the best option. – DaTval Sep 07 '18 at 22:49

1 Answers1

6

"Easiest" way to deserialize human friendly 10m, 5s and 1h is likely a combination of regex and Java code.

public static Duration parseHuman(String text) {
    Matcher m = Pattern.compile("\\s*(?:(\\d+)\\s*(?:hours?|hrs?|h))?" +
                                "\\s*(?:(\\d+)\\s*(?:minutes?|mins?|m))?" +
                                "\\s*(?:(\\d+)\\s*(?:seconds?|secs?|s))?" +
                                "\\s*", Pattern.CASE_INSENSITIVE)
                       .matcher(text);
    if (! m.matches())
        throw new IllegalArgumentException("Not valid duration: " + text);
    int hours = (m.start(1) == -1 ? 0 : Integer.parseInt(m.group(1)));
    int mins  = (m.start(2) == -1 ? 0 : Integer.parseInt(m.group(2)));
    int secs  = (m.start(3) == -1 ? 0 : Integer.parseInt(m.group(3)));
    return Duration.ofSeconds((hours * 60L + mins) * 60L + secs);
}

Test

System.out.println(parseHuman("1h"));
System.out.println(parseHuman("1 hour 200 minutes"));
System.out.println(parseHuman("3600 secs"));
System.out.println(parseHuman("2h3m4s"));

Output

PT1H
PT4H20M
PT1H
PT2H3M4S
Andreas
  • 154,647
  • 11
  • 152
  • 247
  • Thanks Adreas, good solution. For now I just use: `Duration.parse(("PT" + text).toUpperCase())`, but your solution does cover more cases and fails in a better way. – DaTval Sep 07 '18 at 22:44