简体   繁体   中英

Jackson, howto deserialize fields with custom name?

I try to deserialize Jira issues from the REST API into an object. Thats quite straight forward. Where I struggle is mapping a custom field in Jira onto a property. I've tried using a custom deserializer but it does not "kick in".

This is how the Json from the REST call looks like: (some parts stripped)

{
  "expand": "renderedFields,names,schema,...",
  "id": "53899",
  "key": "DPT-12",
  "fields": {
    "issuetype": {
      "id": "10001",
      "name": "Story",
      "subtask": false
    },
    "timespent": null,
    "project": {
      "id": "10823",
      "key": "DPT"
    },
    "fixVersions": [],
    "customfield_10111": null,
    "aggregatetimespent": null,
    "resolution": null,
    "customfield_10112": null,
    "customfield_10700": [
      "entwicklung-w"
    ],
    "customfield_10304": null,
    "resolutiondate": null,
    "lastViewed": "2017-04-04T14:34:19.868+0200",
    "created": "2017-02-02T12:01:31.443+0100",
    "priority": {
      "name": "Schwer",
      "id": "10001"
    },
    "assignee": {
      "displayName": "me :-)"
    },
    "updated": "2017-04-04T14:34:19.710+0200",
    "status": {
      "iconUrl": "https://jira.mobi.ch/",
      "name": "Backlog",
      "statusCategory": {
        "name": "Aufgaben"
      }
    },
    "summary": "Ereignisse in rocket Chat schreiben",
    "creator": {
      "displayName": "me :-)"
    },
    "reporter": {
      "displayName": "me :-)"
    }
  }
}

The custom field name is configured in my application ("customfield_10700") and I want to map it on the property:

private Set<String> deploymentEnvironments;

So here are the relevant Dto's and the test class (getters and setter stripped here).

Test:

import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.hamcrest.collection.IsEmptyCollection.empty;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNot.not;
import static org.hamcrest.core.IsNull.nullValue;
import static org.junit.Assert.assertThat;

import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Set;

import com.fasterxml.jackson.databind.module.SimpleModule;
import org.junit.Test;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

public class IssueFieldsWithDeserializerTest {

    @Test
    public void testJiraResponseDeserializer() throws IOException, URISyntaxException {
        // arrange
        String deploymentEnvsKey = "customfield_10700";

        String json = new String(Files.readAllBytes(Paths.get(getClass().getClassLoader().getResource("jira-example-issue-with-customfield-poc.json").toURI())));

        ObjectMapper mapper = new ObjectMapper();
        mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        SimpleModule module = new SimpleModule();
        module.addDeserializer(Set.class, new CustomFieldDeserializer(deploymentEnvsKey));
        mapper.registerModule(module);


        // act
        IssueResponsePoc issue = mapper.readValue(json, IssueResponsePoc.class);


        // assert
        assertThat("issue is not null", issue, is(not(nullValue())));
        assertThat("fields are not null", issue.getFields(), is(not(nullValue())));
        assertThat("custom field is not null", issue.getFields().getDeploymentEnvironments(), is(not(nullValue())));
        assertThat("custom field is not empty", issue.getFields().getDeploymentEnvironments(), is(not(empty())));
        assertThat("custom field has one value", issue.getFields().getDeploymentEnvironments(), hasSize(1));
    }

}

IssueResponsePoc class:

import java.io.Serializable;

import com.fasterxml.jackson.annotation.JsonProperty;

public class IssueResponsePoc implements Serializable {

    @JsonProperty private String id;
    @JsonProperty private String key;

    @JsonProperty private IssueFieldsPoc fields;

}

Interesting class: IssueFieldsPoc

import java.io.Serializable;
import java.util.Date;
import java.util.List;
import java.util.Set;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

import com.fasterxml.jackson.annotation.JsonProperty;

public class IssueFieldsPoc implements Serializable {

    @JsonProperty private String summary;
    @JsonProperty private IssueType issuetype;
    @JsonProperty private IssueUser creator;
    @JsonProperty private Date created;
    @JsonProperty private IssueUser reporter;
    @JsonProperty private IssuePriority priority;
    @JsonProperty private IssueResolution resolution;
    @JsonProperty private List<String> labels;
    @JsonProperty private Date resolutiondate;
    @JsonProperty private IssueUser assignee;
    @JsonProperty private Date updated;
    @JsonProperty private IssueStatus status;

    @JsonDeserialize private Set<String> deploymentEnvironments;
    // @JsonDeserialize(using = CustomFieldDeserializer.class) private Set<String> deploymentEnvironments;

    public Set<String> getDeploymentEnvironments() {
        return deploymentEnvironments;
    }

    public void setDeploymentEnvironments(Set<String> deploymentEnvironments) {
        this.deploymentEnvironments = deploymentEnvironments;
    }
}

My deserializer:

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;

import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;

public class CustomFieldDeserializer extends StdDeserializer<Set<String>> {

    private final String customFieldName;

    public CustomFieldDeserializer(String customFieldName) {
        super((Class<?>) null);
        this.customFieldName = customFieldName;
    }

    @Override
    public Set<String> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        System.out.println("deserializer started!");
        return null;
    }

    @Override
    public Collection<Object> getKnownPropertyNames() {
        return Collections.singletonList(customFieldName);
    }
}

I tried registering a custom deserializer but it does not start, I suspect its ignored because jackson cannot identify the field name. Adding the "getKnownPropertyNames" method did not help. Since I need to put the jira custom field name (I read it from a configuration) somewhere I tried to put it into the deserializer. Using the jackson annotation @JsonDeserialize.

I also tried wrapping it into another class and not using Set directly to have a stronger typing. No luck either.

I also tried configuring the deserializer within the annotation but that requires a default constructor and I can no longer configure the jira custom field name.

The current solution uses the @JsonAnySetter annotation:

@JsonAnySetter
public void setCustomProperty(String name, Object value) {
    if(StringUtils.startsWith(name, "customfield_")) {
        this.customFields.put(name, value);
    }
}

But I would prefer having that logic within the deserializer.

Is there a way to help jackson when to start the deserializer (since it knows the property name) for this dynamic property name?

Update: Registered the module to the mapper. As suggested in the answers adding the exact property name to the field:

@JsonProperty("customfield_10700")
@JsonDeserialize
private Set<String> deploymentEnvironments;

will allow the deserializer to start. But as mentioned above that is a configurable value I cannot put (or I dont want to) directly in the mapping code.

Well, if I understand right, you need to transform json to java object.

If you want that class ignore unknown properties you need add @JsonIgnoreProperties(ignoreUnknown = true) to your classes which must ignore ( IssueResponsePoc only or IssueFieldsPoc too).

In @JsonProperty(value = <name_of_property_in_json>) will allow you to use any name for your field in java class.

If you repeat nested levels of json by java classes with corresponding annotations ( @JsonProperty , @JsonIgnore and so on) you don't need to use deserializer an whole.

And if you want to process unknown fields in your classes, you can use @JsonAnySetter for this purposes

I think your issue can be resolved by setting @JsonProperty("customfield_10700") to the field deploymentEnvironments as shown below. You don't need a custom deserializer in this case.

public class IssueFieldsPoc implements Serializable {

    @JsonProperty private String summary;
    @JsonProperty private Date created;
    @JsonProperty private List<String> labels;
    @JsonProperty private Date resolutiondate;

    @JsonProperty private Date updated;

    @JsonProperty("customfield_10700")
    private Set<String> deploymentEnvironments;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM