Подтвердить что ты не робот

Джексон - настраиваемый сериализатор, который переопределяет только определенные поля

Я знаю, как использовать пользовательский сериализатор в Jackson (расширяя JsonSerializer), но я хочу, чтобы сериализатор по умолчанию работал для всех полей, за исключением только одного поля, которое я хочу переопределить с помощью настраиваемого сериализатора.

Аннотации не являются опцией, потому что я сериализую сгенерированный класс (из Thrift).

Как указать только определенные поля для переопределения при записи пользовательского сериализатора джексона?

Update:

Здесь класс, который я хочу сериализовать:

class Student {
    int age;
    String firstName;
    String lastName;
    double average;
    int numSubjects

    // .. more such properties ...
}

Вышеупомянутый класс имеет много свойств, большинство из которых используют родные типы. Я хочу просто переопределить несколько свойств в пользовательском сериализаторе и позволить Джексону справляться с остальными, как обычно. Напр. Я просто хочу преобразовать поле "age" в пользовательский вывод.

4b9b3361

Ответ 1

Я столкнулся с той же проблемой, и я решил ее с помощью CustomSerializerFactory.

Этот подход позволяет игнорировать определенное поле для всех объектов или для определенных типов.

public class EntityCustomSerializationFactory extends CustomSerializerFactory {

    //ignored fields
    private static final Set<String> IGNORED_FIELDS = new HashSet<String>(
            Arrays.asList(
                    "class",
                    "value",
                    "some"
            )
    );


    public EntityCustomSerializationFactory() {
        super();
    }

    public EntityCustomSerializationFactory(Config config) {
        super(config);
    }

    @Override
    protected void processViews(SerializationConfig config, BeanSerializerBuilder builder) {
        super.processViews(config, builder);

        //ignore fields only for concrete class
        //note, that you can avoid or change this check
        if (builder.getBeanDescription().getBeanClass().equals(Entity.class)){
            //get original writer
            List<BeanPropertyWriter> originalWriters = builder.getProperties();

            //create actual writers
            List<BeanPropertyWriter> writers = new ArrayList<BeanPropertyWriter>();

            for (BeanPropertyWriter writer: originalWriters){
                String propName = writer.getName();

                //if it isn't ignored field, add to actual writers list
                if (!IGNORED_FIELDS.contains(propName)){
                    writers.add(writer);
                }
            }

            builder.setProperties(writers);
        }

    }
}

И после этого вы можете использовать его примерно так:

objectMapper.setSerializerFactory(new EntityCustomSerializationFactory());
objectMapper.writeValueAsString(new Entity());//response will be without ignored fields

Ответ 2

Предполагая, что ваш целевой класс

public class Student {
    int age;
    String firstName;
    String lastName;
    double average;
    int numSubjects;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public double getAverage() {
        return average;
    }

    public void setAverage(double average) {
        this.average = average;
    }

    public int getNumSubjects() {
        return numSubjects;
    }

    public void setNumSubjects(int numSubjects) {
        this.numSubjects = numSubjects;
    }

}

Вам нужно написать собственный сериализатор, как указано ниже

public class MyCustomSerializer extends JsonSerializer<Student> {

    @Override
    public void serialize(Student value, JsonGenerator jgen,
            SerializerProvider provider) throws IOException,
            JsonProcessingException {
        if (value != null) {
            jgen.writeStartObject();
            jgen.writeStringField("age", "Age: " + value.getAge()); //Here a custom way to render age field is used
            jgen.writeStringField("firstName", value.getFirstName());
            jgen.writeStringField("lastName", value.getLastName());
            jgen.writeNumberField("average", value.getAverage());
            jgen.writeNumberField("numSubjects", value.getNumSubjects());
            //Write other properties
            jgen.writeEndObject();
        }
    }

}

затем добавьте его в ObjectMapper

ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("custom",
        Version.unknownVersion());
module.addSerializer(Student.class, new MyCustomSerializer());
mapper.registerModule(module);

затем используйте его, как

Student s = new Student();
s.setAge(2);
s.setAverage(3.4);
s.setFirstName("first");
s.setLastName("last");
s.setNumSubjects(3);

StringWriter sw = new StringWriter();
mapper.writeValue(sw, s);
System.out.println(sw.toString());

Он произведет o/p как

{ "возраст": "Возраст: 2", "FirstName": "первый", "LastName": "последний", "средний": 3.4, "numSubjects": 3}

Ответ 3

Просто потому, что вы не можете изменять классы НЕ означает, что вы не можете использовать аннотации: просто используйте аннотации микширования. См. эту запись в блоге, например (или google для получения дополнительной информации с помощью аннотаций джексон-микса) для использования этого.

Я специально использовал Jackson с классами protobuf- и бережливость, и они работают очень хорошо. Для более ранних версий Thrift мне пришлось отключить обнаружение "is-setters", методы Thrift генерируют, чтобы определить, было ли задано определенное свойство, но в остальном все работает нормально.

Ответ 4

с помощью @JsonView мы можем определить поля классов моделей для сериализации, которые удовлетворяют минимальным критериям (мы должны определить критерии), так как мы можем иметь один основной класс с 10 свойствами, но только 5 свойств могут быть сериализованы, которые необходимо только для клиентов

Определите наши представления, просто создав следующий класс:

public class Views
{
    static class Android{};
    static class IOS{};
    static class Web{};
}

Аннотированный класс модели с представлениями:

public class Demo 
{
    public Demo() 
    {
    }

@JsonView(Views.IOS.class)
private String iosField;

@JsonView(Views.Android.class)
private String androidField;

@JsonView(Views.Web.class)
private String webField;

 // getters/setters
...
..
}

Теперь нам нужно написать собственный json-конвертер, просто расширив класс HttpMessageConverter от spring как:

    public class CustomJacksonConverter implements HttpMessageConverter<Object> 
    {
    public CustomJacksonConverter() 
        {
            super();
        //this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView(Views.ClientView.class));
        this.delegate.getObjectMapper().configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true);
        this.delegate.getObjectMapper().setSerializationInclusion(Include.NON_NULL);

    }

    // a real message converter that will respond to methods and do the actual work
    private MappingJackson2HttpMessageConverter delegate = new MappingJackson2HttpMessageConverter();

    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
        return delegate.canRead(clazz, mediaType);
    }

    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        return delegate.canWrite(clazz, mediaType);
    }

    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return delegate.getSupportedMediaTypes();
    }

    @Override
    public Object read(Class<? extends Object> clazz,
            HttpInputMessage inputMessage) throws IOException,
            HttpMessageNotReadableException {
        return delegate.read(clazz, inputMessage);
    }

    @Override
    public void write(Object obj, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException 
    {
        synchronized(this) 
        {
            String userAgent = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getHeader("userAgent");
            if ( userAgent != null ) 
            {
                switch (userAgent) 
                {
                case "IOS" :
                    this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView(Views.IOS.class));
                    break;
                case "Android" :
                    this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView(Views.Android.class));
                    break;
                case "Web" :
                    this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView( Views.Web.class));
                    break;
                default:
                    this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView( null ));
                    break;
                }
            }
            else
            {
                // reset to default view
                this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView( null ));
            }
            delegate.write(obj, contentType, outputMessage);
        }
    }

}

Теперь нужно сказать spring использовать этот пользовательский конвертер json, просто поместив его в dispatcher-servlet.xml

<mvc:annotation-driven>
        <mvc:message-converters register-defaults="true">
            <bean id="jsonConverter" class="com.mactores.org.CustomJacksonConverter" >
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

То, как вы сможете решить, какие поля нужно сериализовать.

Thanx

Ответ 5

Если вы не хотите загрязнять вашу модель аннотациями, вы можете использовать миксины.

ObjectMapper mapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.setMixInAnnotation(Student.class, StudentMixin.class);
mapper.registerModule(simpleModule);

И вы хотите переопределить поле идентификатора, например:

public abstract class StudentMixin {
    @JsonSerialize(using = StudentIdSerializer.class)
    public String id;
}

Делайте все, что вам нужно с полем:

public class StudentIdSerializer extends JsonSerializer<Integer> {
    @Override
    public void serialize(Integer integer, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeString(String.valueOf(integer * 2));
    }
}