You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jenkinsfile中如何按指定字段对嵌套Map排序?

Jenkins Pipeline按指定字段排序readJSON返回的嵌套数组

我在Jenkinsfile中通过readJSON获取了一个带嵌套数组的JSON结构,想要对repos数组里的对象按BuildOrder字段排序,但直接套用普通Groovy的sort{it.BuildOrder}写法会报错。

能正常运行但默认排序的代码

这段代码可以执行,但只是按默认规则排序,不是我们需要的BuildOrder顺序:

node {
    stage('Preparation') {
        json = readJSON file: '', text: '{ "repos": [ { "Name": "repo2", "Url": "ssh://host/repo2.git", "BuildOrder": 0}, { "Name": "repo1", "Url": "ssh://host/repo1.git", "BuildOrder": 1} ] }'

        println "json obj is ${json}"               
        json.repos.sort().each { myRepo -> 
            println "myRepo ${myRepo} ${myRepo.Name}"
            myRepo.each { myProperties -> println "myProperties ${myProperties}" }
        }
    }
}

修改后报错的代码

尝试按BuildOrder排序的代码执行失败:

node {
    stage('Preparation') {
        json = readJSON file: '', text: '{ "repos": [ { "Name": "repo2", "Url": "ssh://host/repo2.git", "BuildOrder": 0}, { "Name": "repo1", "Url": "ssh://host/repo1.git", "BuildOrder": 1} ] }'

        println "json obj is ${json}"               
        json.repos.sort(){it.BuildOrder}.each { myRepo -> 
            println "myRepo ${myRepo} ${myRepo.Name}"
            myRepo.each { myProperties -> println "myProperties ${myProperties}" }
        }
    }
}

报错信息(中文翻译)

[Pipeline] {
[Pipeline] stage
[Pipeline] { (Preparation)
[Pipeline] readJSON
[Pipeline] echo
json obj is [repos:[[Name:repo2, Url:ssh://host/repo2.git, BuildOrder:0], [Name:repo1, Url:ssh://host/repo1.git, BuildOrder:1]]]
期望调用net.sf.json.JSONArray.sort,但实际捕获到org.jenkinsci.plugins.workflow.cps.CpsClosure2.call
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
附加信息:   org.jenkinsci.plugins.workflow.actions.ErrorAction$ErrorId: d0dc08ff-f1f0-4e76-819f-27e7068774a6
groovy.lang.MissingPropertyException: 未找到字段: java.lang.Integer Name
    at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.unclassifiedField(SandboxInterceptor.java:402)
    at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.onGetProperty(SandboxInterceptor.java:386)
    at org.kohsuke.groovy.sandbox.impl.Checker$7.call(Checker.java:375)
    at org.kohsuke.groovy.sandbox.impl.Checker.checkedGetProperty(Checker.java:379)
    at com.cloudbees.groovy.cps.sandbox.SandboxInvoker.getProperty(SandboxInvoker.java:29)
    at com.cloudbees.groovy.cps.impl.PropertyAccessBlock.rawGet(PropertyAccessBlock.java:20)
    at WorkflowScript.run(WorkflowScript:7)
    at com.cloudbees.groovy.cps.CpsDefaultGroovyMethods.each(CpsDefaultGroovyMethods:2125)
    at com.cloudbees.groovy.cps.CpsDefaultGroovyMethods.each(CpsDefaultGroovyMethods:1980)
    at WorkflowScript.run(WorkflowScript:6)
    at ___cps.transform___(Native Method)
    at com.cloudbees.groovy.cps.impl.PropertyishBlock$ContinuationImpl.get(PropertyishBlock.java:73)
    at com.cloudbees.groovy.cps.LValueBlock$GetAdapter.receive(LValueBlock.java:30)
    at com.cloudbees.groovy.cps.impl.PropertyishBlock$ContinuationImpl.fixName(PropertyishBlock.java:65)
    at jdk.internal.reflect.GeneratedMethodAccessor240.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at com.cloudbees.groovy.cps.impl.ContinuationPtr$ContinuationImpl.receive(ContinuationPtr.java:72)
    at com.cloudbees.groovy.cps.impl.ConstantBlock.eval(ConstantBlock.java:21)
    at com.cloudbees.groovy.cps.Next.step(Next.java:83)
    at com.cloudbees.groovy.cps.Continuable$1.call(Continuable.java:152)
    at com.cloudbees.groovy.cps.Continuable$1.call(Continuable.java:146)
    at org.codehaus.groovy.runtime.GroovyCategorySupport$ThreadCategoryInfo.use(GroovyCategorySupport.java:136)
    at org.codehaus.groovy.runtime.GroovyCategorySupport.use(GroovyCategorySupport.java:275)
    at com.cloudbees.groovy.cps.Continuable.run0(Continuable.java:146)
    at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.access$001(SandboxContinuable.java:18)
    at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.run0(SandboxContinuable.java:51)
    at org.jenkinsci.plugins.workflow.cps.CpsThread.runNextChunk(CpsThread.java:187)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.run(CpsThreadGroup.java:420)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:330)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:294)
    at org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService$2.call(CpsVmExecutorService.java:67)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at hudson.remoting.SingleLaneExecutorService$1.run(SingleLaneExecutorService.java:139)
    at jenkins.util.ContextResettingExecutorService$1.run(ContextResettingExecutorService.java:28)
    at jenkins.security.ImpersonatingExecutorService$1.run(ImpersonatingExecutorService.java:68)
    at jenkins.util.ErrorLoggingExecutorService.lambda$wrap$0(ErrorLoggingExecutorService.java:51)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:829)
Finished: FAILURE

问题原因

Jenkins Pipeline的沙箱环境中,readJSON返回的repos是net.sf.json.JSONArray类型,不是普通的Groovy列表,它的sort方法不支持传递闭包参数;同时直接用点语法(it.Name)访问JSON对象的属性会触发沙箱权限限制,因为这些对象是JSONObject实例,不是标准Groovy Map。

解决方法

方法1:转成普通Groovy列表后排序

把JSONArray转成Groovy原生列表,再用闭包指定排序字段,访问属性时用键值对方式:

node {
    stage('Preparation') {
        def json = readJSON file: '', text: '{ "repos": [ { "Name": "repo2", "Url": "ssh://host/repo2.git", "BuildOrder": 0}, { "Name": "repo1", "Url": "ssh://host/repo1.git", "BuildOrder": 1} ] }'

        println "json obj is ${json}"               
        // 转换为Groovy列表并按BuildOrder排序
        def sortedRepos = json.repos.toList().sort { it.BuildOrder }
        sortedRepos.each { myRepo -> 
            println "myRepo ${myRepo} ${myRepo['Name']}"
            myRepo.each { key, value -> println "myProperties ${key}: ${value}" }
        }
    }
}

方法2:使用Java Collections排序

借助Java的Collections.sort方法,传入比较器来实现按BuildOrder排序:

node {
    stage('Preparation') {
        def json = readJSON file: '', text: '{ "repos": [ { "Name": "repo2", "Url": "ssh://host/repo2.git", "BuildOrder": 0}, { "Name": "repo1", "Url": "ssh://host/repo1.git", "BuildOrder": 1} ] }'

        println "json obj is ${json}"               
        // 用Java Collections指定比较规则排序
        Collections.sort(json.repos, new Comparator() {
            int compare(a, b) {
                return a.BuildOrder <=> b.BuildOrder
            }
        })
        json.repos.each { myRepo -> 
            println "myRepo ${myRepo} ${myRepo['Name']}"
            myRepo.each { key, value -> println "myProperties ${key}: ${value}" }
        }
    }
}

注意事项

  • 访问JSON对象属性时,优先用it['KeyName']的方式,避免点语法触发沙箱权限问题;
  • 如果toList()方法在你的环境中无法使用,可以尝试用json.repos.collect()生成新的Groovy列表。

内容的提问来源于stack exchange,提问作者carl verbiest

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 23:27:35