Я хочу использовать Jackson для десериализации моего JSON из Jira в набор POJO. У меня есть большая часть того, что я хочу, чтобы красиво работало, теперь мне просто нужно декодировать значения настраиваемых полей.
Мой входной JSON выглядит так:
{
"expand": "renderedFields,names,schema,operations,editmeta,changelog,versionedRepresentations",
"id": "104144",
"self": "https://jira.internal.net/rest/api/2/issue/104144",
"key": "PRJ-524",
"fields": {
"summary": "Redo unit tests to load from existing project",
"components": [],
"customfield_10240": {
"self": "https://jira.internal.net/rest/api/2/customFieldOption/10158",
"value": "Normal",
"id": "10158"
}
}
Я могу тривиально загрузить сводку и компоненты, так как я заранее знаю, как называются эти элементы JSON, и могу определить их в моем POJO:
@JsonIgnoreProperties({ "expand", "self", "id", })
public class JiraJson
{
private JiraFields fields;
private String key;
public JiraFields getFields()
{
return fields;
}
public String getKey()
{
return key;
}
public void setFields(JiraFields newFields)
{
fields = newFields;
}
public void setKey(String newKey)
{
key = newKey;
}
}
Аналогично для JiraFields:
@JsonIgnoreProperties({ "issuetype", "priority", "status" })
public class JiraFields
{
private List<JiraComponent> components;
private String summary;
public List<JiraComponent> getComponents()
{
return components;
}
public String getSummary()
{
return summary;
}
public void setComponents(List<JiraComponent> newComponents)
{
components = newComponents;
}
public void setSummary(String newSummary)
{
summary = newSummary;
}
}
Однако поле custom_10240 на самом деле различается в зависимости от того, с какой системой Jira оно запускается: на одной это custom_10240, на другой - custom_10345, поэтому я не могу жестко закодировать это в POJO. Используя другой вызов, можно узнать во время выполнения до начала десериализации, каково имя поля, но это невозможно во время компиляции.
Предполагая, что я хочу отобразить поле value в String на JiraFields под названием Importance, как мне это сделать? Или, может быть, проще, как сопоставить этот Importance с классом JiraCustomField?




Вы можете использовать метод, аннотированный @JsonAnySetter, который принимает все свойства, которые не определены (и не игнорируются). в случае объекта Json (например, настраиваемого поля в вопросе) Джексон передает Map, который содержит все свойства объекта (он может даже содержать значения Map в случае вложенных объектов). Теперь вы можете во время выполнения извлекать любые свойства, которые хотите:
@JsonIgnoreProperties({ "issuetype", "priority", "status" })
public class JiraFields
{
private List<JiraComponent> components;
private String summary;
private String importance;
// getter/setter omitted for brevity
@JsonAnySetter
public void setCustomField(String name, Object value) {
System.out.println(name); // will print "customfield_10240"
if (value instanceof Map) { // just to make sure we got a Json Object
Map<String, Object> customfieldMap = (Map<String, Object>)value;
if (customfieldMap.containsKey("value")) { // check if object contains "value" property
setImportance(customfieldMap.get("value").toString());
}
}
}
}
Спасибо за этот ответ @SharonBenAsher, моя проблема в том, что в реальном XML есть несколько из этих настраиваемых полей. Есть ли способ передать состояние десериализатору, или мне просто нужно иметь дело с глобальной статикой, чтобы получить нужную мне информацию?
Почему вы говорите XML? вопрос указывает JSON? все настраиваемые поля будут отправлены в один и тот же setCustomField(), и вы можете вводить аргументы и извлекать любую информацию, которую хотите
Мысленная ошибка ... это JSON, а не XML. Проблема в том, что если мне нужно прочитать сопоставление name → fieldName из глобальной статики, тогда я могу разговаривать только с одной системой Jira за раз, и мне нужно ввести сериализацию вокруг этого. В идеале я мог бы установить что-то в ObjectMapper, которое я мог бы использовать для переназначения customfield_10240 → importance, тогда состояние было бы локальным, и мне не нужно было бы беспокоиться о проблемах с потоками.
После дальнейших поисков я наконец нашел Аннотация JsonAlias. Это все еще определяется во время компиляции, но у меня было кое-что, что я мог бы поискать дальше!
Дальнейший поиск, и я нашел PropertyNamingStrategy, который позволяет вам переименовать то, какое имя поля JSON ожидается для установщика / поля. Это имеет то преимущество, что это делается с помощью метода, и класс может быть создан во время выполнения.
Вот класс, который я использовал для этого сопоставления:
import java.util.Map;
import java.util.stream.Collectors;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
public final class CustomFieldNamingStrategy
extends PropertyNamingStrategy
{
private static final long serialVersionUID = 8263960285216239177L;
private final Map<String, String> fieldRemapping;
private final Map<String, String> reverseRemapping;
public CustomFieldNamingStrategy(Map<String, String> newFieldRemappings)
{
fieldRemapping = newFieldRemappings;
reverseRemapping = fieldRemapping.entrySet()//
.stream()//
.collect(Collectors.toMap(Map.Entry::getValue,
Map.Entry::getKey));
}
@Override
public String nameForField(MapperConfig<?> config, AnnotatedField field, String defaultName)
{
if (field.getDeclaringClass().getName().equals(JiraFields.class.getName()))
{
return reverseRemapping.getOrDefault(defaultName, defaultName);
}
return defaultName;
}
@Override
public String nameForSetterMethod(MapperConfig<?> config, AnnotatedMethod method,
String defaultName)
{
if (method.getDeclaringClass().getName().equals(JiraFields.class.getName()))
{
return reverseRemapping.getOrDefault(defaultName, defaultName);
}
return defaultName;
}
@Override
public String nameForGetterMethod(MapperConfig<?> config, AnnotatedMethod method,
String defaultName)
{
if (method.getDeclaringClass().getName().equals(JiraFields.class.getName()))
{
return reverseRemapping.getOrDefault(defaultName, defaultName);
}
return defaultName;
}
}
JIRA отправляет клиент REST с уже определенными объектами. Вы изучали это использование?