4
votes

Working on some translation / mapping functionality using Maps/JsonBuilder in Groovy.

Is is possible (without creating extra code outside of the map literal creation) .. to conditionally include/exclude certain key/value pairs ? Some thing along the lines of the following ..

 def someConditional = true   

 def mapResult = 
        [
           "id":123,
           "somethingElse":[],
           if(someConditional){ return ["onlyIfConditionalTrue":true]}
        ]

Expected results: If someConditional if false, only 2 key/value pairs will exist in mapResult.

If someConditional if true, all 3 key/value pairs will exist.

Note that I'm sure it could be done if I create methods / and split things up.. for to keep things concise I would want to keep things inside of the map creation.

5

5 Answers

4
votes

You can help yourself with with:

[a:1, b:2].with{
    if (false) {
        c = 1
    }
    it
}

With a small helper:

Map newMap(m=[:], Closure c) {
    m.with c
    m
}

E.g.:

def m = newMap {
    a = 1
    b = 1
    if (true) {
        c = 1
    }
    if (false) {
        d = 1
    }
}

assert m.a == 1
assert m.b == 1
assert m.c == 1
assert !m.containsKey('d')

Or pass an initial map:

newMap(a:1, b:2) {
    if (true) {
        c = 1
    }
    if (false) {
        d = 1
    }
}

edit

Since Groovy 2.5, there is an alternative for with called tap. It works like with but does not return the return value from the closure, but the delegate. So this can be written as:

[a:1, b:2].tap{
    if (false) {
        c = 1
    }
}
1
votes

There is no such syntax, the best you can do is

def someConditional = true   

def mapResult = [        
  "id":123,
  "somethingElse":[]
]

if (someConditional) {
  mapResult.onlyIfConditionalTrue = true
}
1
votes

You could potentially map all false conditions to a common key (e.g. "/dev/null", "", etc) and then remove that key afterwards as part of a contract. Consider the following:

def condA = true   
def condB = false   
def condC = false   

def mapResult = 
[
   "id":123,
   "somethingElse":[],
   (condA ? "condA" : "") : "hello",
   (condB ? "condB" : "") : "abc",
   (condB ? "condC" : "") : "ijk",
]

// mandatory, arguably reasonable
mapResult.remove("")

assert 3 == mapResult.keySet().size()
assert 123 == mapResult["id"]
assert [] == mapResult["somethingElse"]
assert "hello" == mapResult["condA"]
1
votes

I agree with Donal, without code outside of map creation it is difficult.

At least you would have to implement your own ConditionalMap, it is a little work but perfectly doable.

Each element could have it's own condition like

map["a"] = "A"
map["b"] = "B"
map.put("c","C", true)
map.put("d","D", { myCondition })


   etc...

Here an incomplete example (I did only put, get, keySet, values and size to illustrate, and not typed - but you probably don't need types here?), you will probably have to implement few others (isEmpty, containsKey etc...).

       class ConditionalMap extends HashMap {

        /** Default condition can be a closure */
        def defaultCondition = true

        /** Put an elemtn with default condition */
        def put(key, value) {
            super.put(key, new Tuple(defaultCondition, value))
        }

        /** Put an elemetn with specific condition */
        def put(key, value, condition) {
            super.put(key, new Tuple(condition, value))
        }

        /** Get visible element only */
        def get(key) {
            def tuple = super.get(key)
            tuple[0] == true ? tuple[1] : null
        }

        /** Not part of Map , just to know the real size*/
        def int realSize() {
            super.keySet().size()
        }

        /** Includes only the "visible" elements keys */
        def Set keySet() {
            super.keySet().inject(new HashSet(),
                    { result, key
                        ->
                        def tuple = super.get(key)
                        if (tuple[0])
                            result.add(key)
                        result
                    })
        }

        /** Includes only the "visible" elements keys */
        def Collection values() {
            this.keySet().asCollection().collect({ k -> this[k] })
        }

        /** Includes only the "visible" elements keys */
        def int size() {
            this.keySet().size()
        }
    }

    /** default condition that do not accept elements */
    def map = new ConditionalMap(defaultCondition: false)

    /** condition can be a closure too */
    // def map = new ConditionalMap(defaultCondition : {-> true == false })


    map["a"] = "A"
    map["b"] = "B"
    map.put("c","C", true)
    map.put("d","D", false)

    assert map.size() == 1
    assert map.realSize() == 4

    println map["a"]
    println map["b"]
    println map["c"]
    println map["d"]

    println "size: ${map.size()}"
    println "realSize: ${map.realSize()}"
    println "keySet: ${map.keySet()}"
    println "values: ${map.values()}"

    /** end of script */
0
votes

You can use the spread operator to do this for for both maps and lists:

def t = true

def map = [
    a:5,
    *:(t ? [b:6] : [:])
]

println(map)
[a:5, b:6]

This works in v3, haven't tried in prior versions.