Джексон - настраиваемый сериализатор, который переопределяет только определенные поля
Я знаю, как использовать пользовательский сериализатор в Jackson (расширяя JsonSerializer
), но я хочу, чтобы сериализатор по умолчанию работал для всех полей, за исключением только одного поля, которое я хочу переопределить с помощью настраиваемого сериализатора.
Аннотации не являются опцией, потому что я сериализую сгенерированный класс (из Thrift).
Как указать только определенные поля для переопределения при записи пользовательского сериализатора джексона?
Update:
Здесь класс, который я хочу сериализовать:
class Student {
int age;
String firstName;
String lastName;
double average;
int numSubjects
// .. more such properties ...
}
Вышеупомянутый класс имеет много свойств, большинство из которых используют родные типы. Я хочу просто переопределить несколько свойств в пользовательском сериализаторе и позволить Джексону справляться с остальными, как обычно. Напр. Я просто хочу преобразовать поле "age" в пользовательский вывод.
Ответы
Ответ 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));
}
}