您如何使用 Jackson 将 Java Map 转换为组合的 POJO?

How do you use Jackson to convert a Java Map to a composed POJO?

我一直在试验更可靠的方法来解析来自 Conductor 工作流的输入。为此,我创建了一个包含一个人和他们的车辆的组合 class 结构。

Person.java

public class Person {

    protected String firstName;
    protected String lastName;
    protected List<Vehicle> vehicles;

    // Constructors, getters, setters

}

Vehicle.java

public class Vehicle {

    public String make;
    public String model;
    public String trim;
    public short year;

    // Constructors, getters, setters

}

然后我创建了一个 worker,它使用 Jackson 将 Conductor Client 的 Task.getInputData() Map 转换为上述 POJO。

ExperimentalStepWorker.java

public class ExperimentStepWorker implements Worker {

    protected ObjectMapper objectMapper = new ObjectMapper();

    // Other @Overloaded methods per the Worker interface

    /**
     * Executes a task and returns the updated task.
     *
     * @param task Task to be executed.
     * @return the {@link TaskResult} object
     * If the task is not completed yet, return with the status as IN_PROGRESS.
     */
    @Override
    public TaskResult execute(Task task) {

        TaskResult result = new TaskResult(task);

        Person person = objectMapper.convertValue(task.getInputData(), Person.class);

        System.out.println("The map's contents were:");
        System.out.println(task.getInputData());
        System.out.println();
        System.out.println("The objects's contents were:");
        System.out.println(person);

        result.setStatus(TaskResult.Status.COMPLETED);

        return result;
    }
}

然后我将以下工作流提交给了 Conductor。第一个没有输入。第二个只有一个 Person 对象,最后一个也是最后一个有三个车辆的数组:

从 Postman 到 Conductor Server 的工作流输入

{
    "name": "see_if_jackson_works",
    "workflowDef": {
        "ownerApp": "postman",
        "createdBy": "postman_user",
        "name": "do_all_the_things_2",
        "description": "Test Http Task Workflow",
        "version": 1,
        "tasks": [
            {
                "name": "worker_1",
                "taskReferenceName": "First Experiment",
                "inputParameters": {},
                "type": "SIMPLE",
                "startDelay": 15,
                "optional": false
            },
            {
                "name": "worker_2",
                "taskReferenceName": "Second Experiment",
                "inputParameters": {
                    "firstName": "Kirkland",
                    "lastName": "Yuknis"
                },
                "type": "SIMPLE",
                "startDelay": 30,
                "optional": false
            },
            {
                "name": "worker_3",
                "taskReferenceName": "Third experiment",
                "inputParameters": {
                    "firstName": "Kirkland",
                    "lastName": "Yuknis",
                    "vehicles": [
                        {"make": "Subaru", "model": "Outback", "trim": "Limited", "year": 2018},
                        {"make": "Subaru", "model": "Outback", "trim": "Limited", "year": 2018},
                        {"make": "Subaru", "model": "Outback", "trim": "Limited", "year": 2018}
                        ]
                },
                "type": "SIMPLE",
                "startDelay": 45,
                "optional": false
            }
        ],
        "schemaVersion": 2,
        "restartable": true,
        "workflowStatusListenerEnabled": false
    },
    "input": {}
}

前两个实验似乎运行良好,但在最后一个实验中,Conductor 未能执行任务。阅读文档后,我想知道是否可以将组合对象作为 Conductor 任务的输入。有谁能证实或否认这一点?如果可以的话,我可能哪里做错了?

服务器调用worker时execute()方法的Worker输出

The map's contents were:
{}

The objects's contents were:
Person{firstName='null', lastName='null', vehicles=null}

The map's contents were:
{firstName=Kirkland, lastName=Yuknis}

The objects's contents were:
Person{firstName='Kirkland', lastName='Yuknis', vehicles=null}

更新:我刚刚发现了以下错误,看起来像是 Jackson 的问题

{
         "taskType": "worker_3",
         "status": "FAILED",
         "inputData": {
            "firstName": "Kirkland",
            "lastName": "Yuknis",
            "vehicles": [
               {
                  "make": "Subaru",
                  "model": "Outback",
                  "trim": "Limited",
                  "Year": 2018
               },
               {
                  "make": "Subaru",
                  "model": "Outback",
                  "trim": "Limited",
                  "Year": 2018
               },
               {
                  "make": "Subaru",
                  "model": "Outback",
                  "trim": "Limited",
                  "Year": 2018
               }
            ]
         },
         "referenceTaskName": "Third experiment",
         "retryCount": 0,
         "seq": 3,
         "pollCount": 1,
         "taskDefName": "worker_3",
         "scheduledTime": 1559838042119,
         "startTime": 1559838087887,
         "endTime": 1559838088012,
         "updateTime": 1559838087888,
         "startDelayInSeconds": 45,
         "retried": true,
         "executed": false,
         "callbackFromWorker": true,
         "responseTimeoutSeconds": 1200,
         "workflowInstanceId": "08a7bf21-6f4b-4265-97b0-0ce9cc93d1a4",
         "workflowType": "do_all_the_things_2",
         "taskId": "3b4906df-7136-4669-a8c8-a12a9b7c2f6a",
         "reasonForIncompletion": "Error while executing the task: java.lang.IllegalArgumentException: Unrecognized field \"vehicles\" (class com.yuknis.egypt.models.Person), not marked as ignorable (2 known properties: \"lastName\", \"firstName\"])\n at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: com.yuknis.egypt.models.Person[\"vehicles\"])",
         "callbackAfterSeconds": 0,
         "workerId": "Kirklands-MBP.hsd1.fl.comcast.net",
         "workflowTask": {
            "name": "worker_3",
            "taskReferenceName": "Third experiment",
            "inputParameters": {
               "firstName": "Kirkland",
               "lastName": "Yuknis",
               "vehicles": [
                  {
                     "make": "Subaru",
                     "model": "Outback",
                     "trim": "Limited",
                     "Year": 2018
                  },
                  {
                     "make": "Subaru",
                     "model": "Outback",
                     "trim": "Limited",
                     "Year": 2018
                  },
                  {
                     "make": "Subaru",
                     "model": "Outback",
                     "trim": "Limited",
                     "Year": 2018
                  }
               ]
            },
            "type": "SIMPLE",
            "startDelay": 45,
            "optional": false,
            "taskDefinition": {
               "createTime": 1559837812844,
               "name": "worker_3",
               "retryCount": 3,
               "timeoutSeconds": 0,
               "timeoutPolicy": "TIME_OUT_WF",
               "retryLogic": "FIXED",
               "retryDelaySeconds": 600,
               "responseTimeoutSeconds": 1200,
               "concurrentExecLimit": 100,
               "rateLimitPerFrequency": 50,
               "rateLimitFrequencyInSeconds": 60
            },
            "asyncComplete": false
         },
         "rateLimitPerFrequency": 0,
         "rateLimitFrequencyInSeconds": 0,
         "taskDefinition": {
            "present": true
         },
         "queueWaitTime": 45768,
         "taskStatus": "FAILED",
         "logs": [
            "06/06/19, 16:21:28:008 : java.lang.IllegalArgumentException: Unrecognized field \"vehicles\" (class com.yuknis.egypt.models.Person), not marked as ignorable (2 known properties: \"lastName\", \"firstName\"])\n at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: com.yuknis.egypt.models.Person[\"vehicles\"])\n\tat com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:3750)\n\tat com.fasterxml.jackson.databind.ObjectMapper.convertValue(ObjectMapper.java:3668)\n\tat com.yuknis.egypt.workers.ExperimentStepWorker.execute(ExperimentStepWorker.java:58)\n\tat com.netflix.conductor.client.task.WorkflowTaskCoordinator.execute(WorkflowTaskCoordinator.java:379)\n\tat com.netflix.conductor.client.task.WorkflowTaskCoordinator.lambda$pollForTask(WorkflowTaskCoordinator.java:340)\n\tat java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)\n\tat java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)\n\tat java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)\n\tat java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)\n\tat java.base/java.lang.Thread.run(Thread.java:835)\nCaused by: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field \"vehicles\" (class com.yuknis.egypt.models.Person), not marked as ignorable (2 known properties: \"lastName\", \"firstName\"])\n at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: com.yuknis.egypt.models.Person[\"vehicles\"])\n\tat com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:61)\n\tat com.fasterxml.jackson.databind.DeserializationContext.handleUnknownProperty(DeserializationContext.java:823)\n\tat com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:1153)\n\tat com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownProperty(BeanDeserializerBase.java:1589)\n\tat com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownVanilla(BeanDeserializerBase.java:1567)\n\tat com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:294)\n\tat com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)\n\tat com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:3745)\n\t... 9 more\n"
         ]
      },

我发现了问题。作为一种习惯,我不会为 class 上的 non-primitive 数据成员创建访问器。由于 List 是 non-primitive,我(想都没想)没有为它创建访问器。除此之外,车辆受到保护而不是 public。我已经添加了访问器,它解决了这个问题。感谢所有看过这篇文章的人!