简体   繁体   中英

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?

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.

Groovy has a few features that would allow you to make this logic groovier.

  1. The powerful switch statement, which supports regexes and closures.
  2. The isInteger and isDouble built-in CharSequence methods from the Groovy JDK. Sadly, there's not a strict isBoolean so we'll need to implement that ourselves.
  3. The safe navigation operator to avoid NPEs.

Combining these features...

Class<?> determineValueType(String value) {
    switch (value) {
        case { ['true', 'false'].contains(value?.toLowerCase()) }: 
            return Boolean
        case { value?.isInteger() }: 
            return Integer
        case { value?.isDouble() }: 
            return Double
        case ~/^\d{4}-\d{2}-\d{2} \d{1,2}:\d{2}:\d{2}\.\d{3}$/: 
            return Date
        default: 
            return String
    }
}

assert determineValueType('true') == Boolean
assert determineValueType('false') == Boolean
assert determineValueType('2039230') == Integer
assert determineValueType('203923.0') == Double
assert determineValueType('2016-07-26 12:00:00.000') == Date
assert determineValueType('foo') == String

I used a regex instead of SimpleDateFormat to avoid having to catch an Exception. It probably has slightly difference semantics, but you could alternatively create a helper method that returns false if there's an Exception thrown by Date.parse .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM