如何在Groovy中迭代嵌套地图的所有值

时间:2018-07-23 23:24:56

标签: groovy

我有一个未知结构的嵌套地图,目标是遍历该地图中的每个值,检查某个条件(例如null)的值,然后将其替换为其他值。如果地图结构已知,我可以看到如何做,但是这里的问题是它是未知的。

例如,这可以是传递的地图结构(或可以具有任意数量的嵌套地图):

​def map = [
          a:5,
          b:"r",
          c:[ a1:0, b1:null ],
          d:[ a2: [ a3:"", b3:99 ], b2:null ],
          ...
]

通常对于一个简单的地图,将使用this来更新值:

map.each { it.value == null ? it.value = "" : "" }

但是对于嵌套的地图结构,此方法将无效。

是否有一种有效的方法来遍历未知地图的所有嵌套值以调查和更新这些值?

4 个答案:

答案 0 :(得分:4)

您可以同时运行每个,但是随后需要递归获取地图。例如。参见null

deNull

对于一个正确的方法,我还要传递一个“做什么”的闭包,而不是在这里仅仅处理“去空”(这使它可重用)并将其命名为def deNull(def root) { root.each{ if (it.value instanceof Map) { deNull(it.value) } else if (it.value==null) { it.value = "" } } } def map = [ a:5, b:"r", c:[ a1:0, b1:null ], d:[ a2: [ a3:"", b3:99 ], b2:null ], ] println(map.tap{ deNull it }.inspect()) // => ['a':5, 'b':'r', 'c':['a1':0, 'b1':''], 'd':['a2':['a3':'', 'b3':99], 'b2':'']] 或像这样的东西。

答案 1 :(得分:2)

您还可以使用Map.replaceAll(BiFunction<String, Serializable, Serializable>) func)来递归替换未知结构图中的所有null。

考虑以下示例:

import java.util.function.BiFunction

def map = [
        a: 5,
        b: "r",
        c: [a1: 0, b1: null],
        d: [a2: [a3: "", b3: 99], b2: null]
]

def nullsWith(Object replacement) {
    return { String key, Serializable value ->
        if (value instanceof Map) {
            value.replaceAll(nullsWith(replacement))
            return value
        }
        return value == null ? replacement : value
    } as BiFunction<String, Serializable, Serializable>
}

map.replaceAll nullsWith("null replacement")

println map.inspect()

输出:

['a':5, 'b':'r', 'c':['a1':0, 'b1':'null replacement'], 'd':['a2':['a3':'', 'b3':99], 'b2':'null replacement']]

答案 2 :(得分:0)

这是@cfrick上面发布的内容的改进版本。

主要改进:

  1. 它可以遍历包含嵌套的嵌套对象 地图和列表
  2. 它可以用自定义值替换null

def deNull(root, replaceNullWith = "") {
    if (root instanceof List) {
        root.collect {
            if (it instanceof Map) {
                deNull(it, replaceNullWith)
            } else if (it instanceof List) {
                deNull(it, replaceNullWith)
            } else if (it == null) {
                replaceNullWith
            } else {
                it
            }
        }
    } else if (root instanceof Map) {
        root.each {
            if (it.value instanceof Map) {
                deNull(it.value, replaceNullWith)
            } else if (it.value instanceof List) {
                it.value = deNull(it.value, replaceNullWith)
            } else if (it.value == null) {
                it.value = replaceNullWith
            }
        }
    }
}

def map = [
    a:5,
    b:"r",
    c:[ a1:0, b1:null ],
    d:[ a2: [ a3:"", b3:99 ], b2:null, b3: [2, "x", [], [null], null] ],
]

map = deNull(map)
println map.inspect()

答案 3 :(得分:0)

以@rboy的帖子为基础-我正在将Jenkins构建结果序列化为JSON,由于构建结果具有循环引用,我一直遇到堆栈溢出错误。

在构建结果中,有对previousBuildnextBuild的引用。如果作业2具有指向作业1的previousBuild,并且作业1具有指向作业2的nextBuild,则由于存在循环引用,因此无法序列化整个对象。

为避免这种情况,我想要一种可以从对象中删除/替换构建结果类的所有实例的方法。

修改另一篇文章,我提出以下内容:

import org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper

// helpers
isClosure = {it instanceof Closure}
thunkify = {it -> {_ -> it}}
thunkifyValue = {isClosure(it) ? it : thunkify(it)}
makePredicate = {isClosure(it) ? it : it.&equals}
isAnyType = {types, value -> types.any{it.isInstance(value)}}
isMapOrList = isAnyType.curry([List, Map])

def cleanse(root, _predicate, _transform) {
    def predicate = makePredicate(_predicate)
    def transform = thunkifyValue(_transform)

    if (root instanceof List) {
        return root.collect {
            if (isMapOrList(it)) {
                it = cleanse(it, predicate, transform)
            } else if (predicate(it)) {
                it = transform(it)
            }
            return it
        }
    } else if (root instanceof Map) {
        return root.collectEntries {k,v ->
            if (isMapOrList(v)) {
                v = cleanse(v, predicate, transform)
            } else if (predicate(v)) {
                v = transform(v)
            }
            return [(k): v]
        }
    } else {
        return root
    }
}

// basic usage - pass raw values
// jobs is an array of Jenkins job results
// replaces any occurrence of the value null with the value 'replaced null with string'
cleanse(jobs, null, 'replaced null with string')

// advanced usage - pass closures
// We will replace any value that is an instance of RunWrapper
// with the calculated value "Replaced Build Result - $it.inspect()"
cleanse(jobs, {it instanceof RunWrapper}, {"Replaced Build Result - ${it.inspect()}"})

在我的詹金斯结果上调用它之后,我能够将它们序列化为JSON,而无需从循环依赖项中获取StackOverflowError。这就是我在代码中使用cleanse函数的方式:

// testJobs contains all of the results from each `build()` in my pipeline
cleansed = cleanse(testJobs.collect {
    def props = it.properties
    // Something about the formatting of the values in rawBuild will cause StackOverflowError when creating json
    props['rawBuild'] = props['rawBuild'].toString()
    props
}, {it instanceof RunWrapper}, 'Replaced Build Result')
cleansed = cleanse(cleansed, {it instanceof Class}, 'Class stub')
// `cleansed` now has no values that are instances of RunWrapper and no values that are classes
// both of those will cause issues during JSON serialization

// render JSON
cleansedJson = groovy.json.JsonOutput.toJson(cleansed)