Как (De)сериализовать поле из объекта на основе аннотации с помощью Jackson?
мне нужно настроить Jackson определенным образом, который я опишу ниже.
требования
- аннотированные поля сериализуются только с их идентификатором:
- если поле является нормальным объектом, сериализуйте его
id
- если поле представляет собой набор объектов, сериализуйте массив
id
- если поле является нормальным объектом, сериализуйте его
- аннотированные поля получают свои имена свойств сериализованными по-разному:
- если поле является нормальным объектом, add
"_id"
суффикс свойство name - если поле представляет собой набор объектов, добавьте
"_ids"
суффикс свойство name
- если поле является нормальным объектом, add
- для аннотации я думал что-то вроде custom
@JsonId
в идеале с дополнительным значением для переопределения имени как@JsonProperty
тут - свойство ID должно быть определено пользователем, либо с помощью:
- уже существующий Jackson's
@JsonIdentityInfo
- или путем создания другой аннотации класса или поля
- или решив, какую аннотацию проверить на
id
обнаруживаемость свойств (полезно для сценариев JPA, например)
- уже существующий Jackson's
- объекты должны быть сериализованы с обернутым корневым значением
- название верблюда должно быть преобразовано в нижний регистр с подчеркиванием
- все это должно быть десериализуемым (путем построения экземпляра только с идентификатором setted)
пример
учитывая эти POJO-объект это:
//Inform Jackson which property is the id
@JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id"
)
public abstract class BaseResource{
protected Long id;
//getters and setters
}
public class Resource extends BaseResource{
private String name;
@JsonId
private SubResource subResource;
@JsonId
private List<SubResource> subResources;
//getters and setters
}
public class SubResource extends BaseResource{
private String value;
//getters and setters
}
возможная сериализация Resource
экземпляр может быть:
{
"resource":{
"id": 1,
"name": "bla",
"sub_resource_id": 2,
"sub_resource_ids": [
1,
2,
3
]
}
}
до сих пор...
-
требование #5 можно выполнить, настроив
ObjectMapper
следующим образом:objectMapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true); objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
а затем с помощью
@JsonRootName("example_root_name_here")
в моем случае это. -
требование #6 может быть достигнуто путем настройки
ObjectMapper
следующим образом:objectMapper.setPropertyNamingStrategy( PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
как вы можете видеть, что есть еще много требований. Для тех, кто интересуется, почему мне нужна такая конфигурация, это потому, что я разрабатываю веб-сервис REST для Эмбер.js (более конкретно данные Ember). Вы были бы очень признательны, если бы вы могли помочь с любым из требований.
спасибо!
1 ответов
большинство (все?) ваших требований можно выполнить через пользу контекстного сериализатора. Принимая один ответ от ContextualDeserializer для сопоставления JSON с различными типами карт с Jackson и Вики Джексона (http://wiki.fasterxml.com/JacksonFeatureContextualHandlers) я смог придумать следующее.
вам нужно начать с аннотации @JsonId, которая является ключом, указывающим, что свойство должно использовать только Свойство id.
import com.fasterxml.jackson.annotation.*;
import java.lang.annotation.*;
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotation // important so that it will get included!
public @interface JsonId {
}
Далее находится фактический ContextualSerializer,который делает тяжелый подъем.
import com.fasterxml.jackson.databind.ser.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.core.*;
import java.io.*;
public class ContextualJsonIdSerializer
extends JsonSerializer<BaseResource>
implements ContextualSerializer/*<BaseResource>*/
{
private ObjectMapper mapper;
private boolean useJsonId;
public ContextualJsonIdSerializer(ObjectMapper mapper) { this(mapper, false); }
public ContextualJsonIdSerializer(ObjectMapper mapper, boolean useJsonId) {
this.mapper = mapper;
this.useJsonId = useJsonId;
}
@Override
public void serialize(BaseResource br, JsonGenerator jgen, SerializerProvider provider) throws IOException
{
if ( useJsonId ) {
jgen.writeString(br.getId().toString());
} else {
mapper.writeValue(jgen, br);
}
}
@Override
public JsonSerializer<BaseResource> createContextual(SerializerProvider config, BeanProperty property)
throws JsonMappingException
{
// First find annotation used for getter or field:
System.out.println("Finding annotations for "+property);
if ( null == property ) {
return new ContextualJsonIdSerializer(mapper, false);
}
JsonId ann = property.getAnnotation(JsonId.class);
if (ann == null) { // but if missing, default one from class
ann = property.getContextAnnotation(JsonId.class);
}
if (ann == null ) {//|| ann.length() == 0) {
return this;//new ContextualJsonIdSerializer(false);
}
return new ContextualJsonIdSerializer(mapper, true);
}
}
этот класс смотрит на BaseResource
свойства и проверяет их, чтобы увидеть, если @JsonId
аннотация присутствует. Если это тогда используется только свойство Id, в противном случае передается в ObjectMapper
используется для сериализации значение. Это важно, потому что если вы пытаетесь использовать картограф ,который (в основном) в контексте ContextualSerializer
затем вы получите переполнение стека поскольку в конечном итоге он будет вызывать эти методы снова и снова.
вы ресурс должен выглядеть примерно следующим образом. Я использовал @JsonProperty
аннотация вместо обертывания функциональности в ContextualSerializer
потому что глупо изобретать велосипед.
import java.util.*;
import com.fasterxml.jackson.annotation.*;
public class Resource extends BaseResource{
private String name;
@JsonProperty("sub_resource_id")
@JsonId
private SubResource subResource;
@JsonProperty("sub_resource_ids")
@JsonId
private List<SubResource> subResources;
//getters and setters
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public SubResource getSubResource() {return subResource;}
public void setSubResource(SubResource subResource) {this.subResource = subResource;}
public List<SubResource> getSubResources() {return subResources;}
public void setSubResources(List<SubResource> subResources) {this.subResources = subResources;}
}
наконец, метод, который выполняет сериализацию, просто создает дополнительный ObjectMapper
и регистрирует модуль в оригинале ObjectMapper
.
// Create the original ObjectMapper
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
// Create a clone of the original ObjectMapper
ObjectMapper objectMapper2 = new ObjectMapper();
objectMapper2.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
objectMapper2.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
objectMapper2.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
// Create a module that references the Contextual Serializer
SimpleModule module = new SimpleModule("JsonId", new Version(1, 0, 0, null));
// All references to SubResource should be run through this serializer
module.addSerializer(SubResource.class, new ContextualJsonIdSerializer(objectMapper2));
objectMapper.registerModule(module);
// Now just use the original objectMapper to serialize