Как (De)сериализовать поле из объекта на основе аннотации с помощью Jackson?

мне нужно настроить Jackson определенным образом, который я опишу ниже.

требования

  1. аннотированные поля сериализуются только с их идентификатором:
    • если поле является нормальным объектом, сериализуйте его id
    • если поле представляет собой набор объектов, сериализуйте массив id
  2. аннотированные поля получают свои имена свойств сериализованными по-разному:
    • если поле является нормальным объектом, add "_id" суффикс свойство name
    • если поле представляет собой набор объектов, добавьте "_ids" суффикс свойство name
  3. для аннотации я думал что-то вроде custom @JsonId в идеале с дополнительным значением для переопределения имени как @JsonProperty тут
  4. свойство ID должно быть определено пользователем, либо с помощью:
    • уже существующий Jackson's @JsonIdentityInfo
    • или путем создания другой аннотации класса или поля
    • или решив, какую аннотацию проверить на id обнаруживаемость свойств (полезно для сценариев JPA, например)
  5. объекты должны быть сериализованы с обернутым корневым значением
  6. название верблюда должно быть преобразовано в нижний регистр с подчеркиванием
  7. все это должно быть десериализуемым (путем построения экземпляра только с идентификатором 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