Search code examples
javadatereflectiongroovyintrospection

Inferring String value type in Groovy


I have a situation where I will be given a String and I need to determine what Class<?> best suits its value given the following constraints:

  • If the String is (ignoring case) equal to "true" or "false", it's a Boolean
  • Else, if the String is an integral number with no decimal point, it's an Integer
  • Else, if the String is a number, it's a Double
  • Else, if the String matches the date time format YYYY-MM-DD hh:mm:ss.sss, then its a Java Date
  • Else it's just a String afterall

My best attempt is nasty and involves a lot of nested try/catch blocks:

// Groovy pseudo-code
Class<?> determineValueType(String value) {
    Class<?> clazz
    if(value.equalsIgnoreCase('true') || value.equalsIgnoreCase('false')) {
        clazz = Boolean
    } else {
        try {
            Integer.parse(value)
            clazz = Integer
        } catch(Exception ex1) {
            try {
                Double.parse(value)
                clazz = Double
            } catch(Exception ex2) {
                try {
                    Date.parse('YYYY-MM-DD hh:mm:ss.sss', value)
                    clazz = Date
                } catch(Exception ex3) {
                    clazz = String
                }
            }
        }
    }

    clazz
}

Are there any Groovier ways of accomplishing this, perhaps something endemic to some obscure Groovy reflection API?


Solution

  • There are two methods which can help you in Groovy's extended String class (actually on CharSequence):

    But for other cases, AFAIK, you are on your own to implement the parsing. You could try working with a map and some closures, to reduce some boilerplate:

    Class parse(val) {
        def convert = [
            (Integer) : { it.toInteger() },
            (Double)  : { it.toDouble() },
            (Date)    : { Date.parse('YYYY-MM-DD hh:mm:ss.sss', it) },
            (Boolean) : { Boolean.parseBoolean it },
            (String)  : { it }
        ]
    
        convert.findResult { key, value ->
            try {
                if (value(val)) return key
            } catch (e) {}
        }
    }
    
    assert parse('9.1') == Double
    assert parse('9') == Integer
    assert parse('1985-10-26 01:22:00.000') == Date // great scott!
    assert parse('chicken') == String
    assert parse('True') == Boolean
    

    Note that if (Double) precedes (Integer) the tests won't work, since 9 is both a double and a integer.