简体   繁体   中英

Groovy collect on a list of strings

I'm trying to create a crude json outputter that takes in a list of key-value pairs and results in the appropriate json. Here's what I've got:

class keyValuePairs {
    private String[][] contents
    keyValuePairs(String[][] inputList) {
        contents = inputList
    }

    public String json() {
        String jsonString = '{\n'
        String[] keyVals = contents collect {jsonPair it}
        jsonString += keyVals.join(',\n') + '\n'
        jsonString += '}'
        return jsonString
    }

    String jsonPair(String[] keyVal) {
        return '  "' + keyVal[0] + '": "' + keyVal[1] + '"'
    }
}

My keyValuesPairs takes in a list of lists of Strings and this compiles fine, but my contents collect {jsonPair it} line fails with:

Caught: groovy.lang.MissingMethodException: No signature of method: auditconfigprototype.keyValuePairs.jsonPair() is applicable for argument types: (auditconfig
prototype.keyValuePairs) values: [auditconfigprototype.keyValuePairs@7fa98a66]
Possible solutions: jsonPair([Ljava.lang.String;), json()

I could fall back on a loop over contents here, but I want to take the functional approach of using collect . However, it seems every attempt (even trivial ones!) to do a collect on my contents list fails. Have I defined the list wrong? How should I use collect over a list of lists of strings?

The exception you're getting is due to this line:

String[] keyVals = contents collect {jsonPair it}

At first glance it may appear to be the equivalent of:

String[] keyVals = contents.collect {jsonPair it}

But in fact, you're getting this:

String[] keyVals = contents(collect() {jsonPair it})

Explanation

When assigning the keyVals variable, the collect() method is being called on the keyValuePairs instance; on this .

collect() then iterates through a List containing a single item, the instance of keyValuePairs , and calls the closure with the keyValuePairs instance. The jsonPair() method is expecting a String array, but receives a keyValuePairs instead. That's what is throwing the exception:

groovy.lang.MissingMethodException: No signature of method: keyValuePairs.jsonPair() is applicable for argument types: (keyValuePairs) values: [keyValuePairs@63169fe0]

Solution

The solution is to simply add the missing period to make the code as follows:

String[] keyVals = contents.collect {jsonPair it}

Groovy-ier solution

As an exercise, here's another way to implement your parser.

def a = [
    ['firstname', 'John'],
    ['lastname', 'Doe'],
    ['language', 'Groovy']
]

class KeyValuePairs {
    def pairs

    String json() {
        new StringBuilder('{\n').with {
            append(
                pairs
                    .collect { String.format('"%s":"%s"', it[0], it[1]) }
                    .collect { "\t$it" }.join(',\n'))

            append '\n}\n'
        }.toString()
    }
}

def parser = new KeyValuePairs(pairs: a)
parser.json()
  • This implementation uses a List < List > instead of an array because Groovy has great built-in support for them.

  • By using Groovy Beans , the KeyValuePairs instance is created without explicitly creating a boiler-plate constructor.

  • A StringBuilder is used instead of String concatenation. Mainly because I think it makes the code look nicer.

  • Since the formatting requires double-quotes, I used String.format() . That keeps the formatting easy to read. Note: normally I'd use a GString .

  • Because the formatting is applied so concisely, I did away with the jsonPair() method.

The first collect() produces an output like this:

[
    '"firstname":"John"', 
    '"lastname":"Doe"', 
    '"language":"Groovy"'
]

And the second collect() adds a TAB in front of the double-quotes of every pair. Finally, the join() builds the single String of pairs.

Additional improvements

Another way to improve the code some more is to replace

it[0], it[1]

with

it.key, it.value

That can be done by replacing the first collect() with these two:

.collect { [['key', 'value'], it].transpose().collectEntries() }
.collect { String.format('"%s":"%s"', it.key, it.value) }

I hope this helps :)

Recommended reading

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