Jenkins管道NotSerializableException:groovy.json.internal.LazyMap

Sun*_*vic 58 groovy json jenkins jenkins-pipeline

解决:感谢S.Richmond的回答.我需要取消设置所有存储的groovy.json.internal.LazyMap类型的映射,这意味着使变量envServersobject使用后无效.

附加:搜索此错误的人可能有兴趣使用Jenkins管道步骤readJSON- 在此处查找更多信息.


我正在尝试使用Jenkins Pipeline从用户那里获取输入,该输入作为json字符串传递给作业.然后Pipeline使用slurper解析它,我挑选出重要的信息.然后,它将使用该信息与不同的作业参数并行多次运行1个作业.

直到我添加下面的代码"## Error when below here is added"脚本运行正常.即使是该点下面的代码也会自行运行.但合并后,我得到以下错误.

我应该注意到被触发的作业被调用并且确实成功运行但是发生了以下错误并且失败了主要作业.因此,主要工作不会等待触发作业的返回.我可以尝试/捕捉周围build job:但我希望主要工作等待触发的工作完成.

谁能在这里协助?如果您需要更多信息,请告诉我们.

干杯

def slurpJSON() {
return new groovy.json.JsonSlurper().parseText(BUILD_CHOICES);
}

node {
  stage 'Prepare';
  echo 'Loading choices as build properties';
  def object = slurpJSON();

  def serverChoices = [];
  def serverChoicesStr = '';

  for (env in object) {
     envName = env.name;
     envServers = env.servers;

     for (server in envServers) {
        if (server.Select) {
            serverChoicesStr += server.Server;
            serverChoicesStr += ',';
        }
     }
  }
  serverChoicesStr = serverChoicesStr[0..-2];

  println("Server choices: " + serverChoicesStr);

  ## Error when below here is added

  stage 'Jobs'
  build job: 'Dummy Start App', parameters: [[$class: 'StringParameterValue', name: 'SERVER_NAME', value: 'TestServer'], [$class: 'StringParameterValue', name: 'SERVER_DOMAIN', value: 'domain.uk'], [$class: 'StringParameterValue', name: 'APP', value: 'application1']]

}
Run Code Online (Sandbox Code Playgroud)

错误:

java.io.NotSerializableException: groovy.json.internal.LazyMap
    at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:860)
    at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:569)
    at org.jboss.marshalling.river.BlockMarshaller.doWriteObject(BlockMarshaller.java:65)
    at org.jboss.marshalling.river.BlockMarshaller.writeObject(BlockMarshaller.java:56)
    at org.jboss.marshalling.MarshallerObjectOutputStream.writeObjectOverride(MarshallerObjectOutputStream.java:50)
    at org.jboss.marshalling.river.RiverObjectOutputStream.writeObjectOverride(RiverObjectOutputStream.java:179)
    at java.io.ObjectOutputStream.writeObject(Unknown Source)
    at java.util.LinkedHashMap.internalWriteEntries(Unknown Source)
    at java.util.HashMap.writeObject(Unknown Source)
...
...
Caused by: an exception which occurred:
    in field delegate
    in field closures
    in object org.jenkinsci.plugins.workflow.cps.CpsThreadGroup@5288c
Run Code Online (Sandbox Code Playgroud)

luk*_*a5z 102

JsonSlurperClassic改用.

从Groovy 2.3(注:Jenkins 2.7.1使用Groovy 2.4.7)JsonSlurper返回LazyMap而不是HashMap.这使得新的实现JsonSlurper 不是线程安全的而不是可序列化的.这使得它在管道DSL脚本中的@NonDSL函数之外无法使用.

但是,您可以回退到groovy.json.JsonSlurperClassic支持旧行为,并且可以在管道脚本中安全地使用.

import groovy.json.JsonSlurperClassic 


@NonCPS
def jsonParse(def json) {
    new groovy.json.JsonSlurperClassic().parseText(json)
}

node('master') {
    def config =  jsonParse(readFile("config.json"))

    def db = config["database"]["address"]
    ...
}    
Run Code Online (Sandbox Code Playgroud)

PS.JsonSlurperClassic在调用它之前,您仍需要批准.

  • JsonSluperClassic ..这个名字讲述了很多关于软件开发的当前状态 (12认同)
  • Jenkins管理员需要导航到管理Jenkins»进程内脚本批准. (5认同)
  • 你能告诉我如何批准`JsonSlurperClassic`吗? (2认同)

S.R*_*ond 49

我今天遇到了这个问题,通过一些暴力,我已经想出了如何解决它以及可能的原因.

可能最好从这个原因开始:

Jenkins有一个范例,可以通过重新启动服务器来中断,暂停和恢复所有作业.为了实现这一点,管道及其数据必须是完全可序列化的 - IE需要能够保存所有内容的状态.类似地,它需要能够序列化构建中节点和子作业之间的全局变量状态,这就是我认为你和我发生的事情,以及为什么只有在你添加额外的构建步骤时它才会发生.

无论出于何种原因,JSONObject默认情况下不可序列化.我不是Java开发人员,所以我不能不遗余力地谈论这个话题.虽然我不知道他们对Groovy和Jenkins有多适用,但有很多答案可以解决这个问题.有关更多信息,请参阅此帖子.

你如何解决它:

如果你知道如何,你可以以某种方式使JSONObject可序列化.否则,您可以通过确保没有该类型的全局变量来解决它.

尝试取消设置objectvar或将其包装在方法中,使其范围不是全局节点.

  • 谢谢,这就是我解决这个问题所需的线索。虽然我已经尝试过你的建议,但它让我再次看,我没有考虑到我将地图的一部分存储在其他变量中 - 这些导致了错误。所以我也需要取消它们。将修改我的问题以包含对代码的正确更改。干杯 (2认同)
  • 每天查看约 8 次。你们介意提供一个更详细的例子来说明如何实现这个解决方案吗? (2认同)
  • 下面的解决方案使用 JsonSlurperClassic 修复了我遇到的完全相同的问题,可能应该是这里批准的选择。这个答案有其优点,但它不是这个特定问题的正确解决方案。 (2认同)

mko*_*bit 12

编辑:正如@Sunvic在评论中所指出的,下面的解决方案不适用于JSON Arrays.

我通过使用JsonSlurper然后HashMap从惰性结果创建一个新的处理这个.HashMapSerializable.

我认为,这需要双方的白名单new HashMap(Map)JsonSlurper.

@NonCPS
def parseJsonText(String jsonText) {
  final slurper = new JsonSlurper()
  return new HashMap<>(slurper.parseText(jsonText))
}
Run Code Online (Sandbox Code Playgroud)

总的来说,我建议只使用Pipeline Utility Steps插件,因为它有一个readJSON步骤可以支持工作区或文本中的文件.


Reg*_*ult 11

我想支持其中一个答案:我建议只使用 Pipeline Utility Steps 插件,因为它有一个 readJSON 步骤,可以支持工作区中的文件或文本:https ://jenkins.io/doc/pipeline/steps /pipeline-utility-steps/#readjson-read-json-from-files-in-the-workspace

script{
  def foo_json = sh(returnStdout:true, script: "aws --output json XXX").trim()
  def foo = readJSON text: foo_json
}
Run Code Online (Sandbox Code Playgroud)

这不需要任何白名单或额外的东西。


Nil*_*oud 8

这是要求的详细答案。

未设置对我有用:

String res = sh(script: "curl --header 'X-Vault-Token: ${token}' --request POST --data '${payload}' ${url}", returnStdout: true)
def response = new JsonSlurper().parseText(res)
String value1 = response.data.value1
String value2 = response.data.value2

// unset response because it's not serializable and Jenkins throws NotSerializableException.
response = null
Run Code Online (Sandbox Code Playgroud)

我从解析的响应中读取值,当我不再需要该对象时,我会取消设置它。


Tom*_*Tom 5

来自@mkobit 的答案的稍微更通用的形式将允许对数组和映射进行解码:

import groovy.json.JsonSlurper

@NonCPS
def parseJsonText(String json) {
  def object = new JsonSlurper().parseText(json)
  if(object instanceof groovy.json.internal.LazyMap) {
      return new HashMap<>(object)
  }
  return object
}
Run Code Online (Sandbox Code Playgroud)

注意:请注意,这只会将顶级 LazyMap 对象转换为 HashMap。任何嵌套的 LazyMap 对象仍将存在并继续导致 Jenkins 出现问题。


小智 5

您可以使用以下函数将 LazyMap 转换为常规 LinkedHashMap(它将保留原始数据的顺序):

LinkedHashMap nonLazyMap (Map lazyMap) {
    LinkedHashMap res = new LinkedHashMap()
    lazyMap.each { key, value ->
        if (value instanceof Map) {
            res.put (key, nonLazyMap(value))
        } else if (value instanceof List) {
            res.put (key, value.stream().map { it instanceof Map ? nonLazyMap(it) : it }.collect(Collectors.toList()))
        } else {
            res.put (key, value)
        }
    }
    return res
}

... 

LazyMap lazyMap = new JsonSlurper().parseText (jsonText)
Map serializableMap = nonLazyMap(lazyMap);

Run Code Online (Sandbox Code Playgroud)

或者更好地使用前面评论中注意到的 readJSON 步骤:

Map serializableMap = readJSON text: jsonText
Run Code Online (Sandbox Code Playgroud)