Вопрос по java, customization, serialization, jackson, json – Джексон: Как добавить пользовательское свойство в JSON без изменения POJO

49

Я разрабатываю REST-интерфейс для моего приложения, используя Джексона для сериализации моих доменных объектов POJO в JSON-представление. Я хочу настроить сериализацию для некоторых типов, чтобы добавить дополнительные свойства к представлению JSON, которых нет в POJO (например, добавить некоторые метаданные, справочные данные и т. Д.). Я знаю, как написать свой собственныйJsonSerializer, но в этом случае мне нужно было бы явно позвонитьJsonGenerator.writeXXX(..) методы длякаждый свойство моего объекта, в то время как все, что мне нужно, это простодобавлять дополнительная собственность. Другими словами, я хотел бы иметь возможность написать что-то вроде:

@Override
public void serialize(TaxonomyNode value, JsonGenerator jgen, SerializerProvider provider) {
    jgen.writeStartObject();
    jgen.writeAllFields(value); // 
Начиная с Jackson-2.5 аннотация JsonAppend может решить эту проблему. Смотрите @Henrikответ ниже Timur Milovanov

Ваш Ответ

11   ответов
1

Мы можем продлитьBeanSerializer, но с маленькой хитростью.

Во-первых, определите класс Java, чтобы обернуть ваш POJO.

@JsonSerialize(using = MixinResultSerializer.class)
public class MixinResult {

    private final Object origin;
    private final Map mixed = Maps.newHashMap();

    @JsonCreator
    public MixinResult(@JsonProperty("origin") Object origin) {
        this.origin = origin;
    }

    public void add(String key, String value) {
        this.mixed.put(key, value);
    }

    public Map getMixed() {
        return mixed;
    }

    public Object getOrigin() {
        return origin;
    }

}

Затем,реализовать свой обычай.serializer

public final class MixinResultSerializer extends BeanSerializer {

    public MixinResultSerializer() {
        super(SimpleType.construct(MixinResult.class), null, new BeanPropertyWriter[0], new BeanPropertyWriter[0]);
    }

    public MixinResultSerializer(BeanSerializerBase base) {
        super(base);
    }

    @Override
    protected void serializeFields(Object bean, JsonGenerator gen, SerializerProvider provider) throws IOException {
        if (bean instanceof MixinResult) {
            MixinResult mixin  = (MixinResult) bean;
            Object      origin = mixin.getOrigin();

            BeanSerializer serializer = (BeanSerializer) provider.findValueSerializer(SimpleType.construct(origin.getClass()));

            new MixinResultSerializer(serializer).serializeFields(origin, gen, provider);

            mixin.getMixed().entrySet()
                    .stream()
                    .filter(entry -> entry.getValue() != null)
                    .forEach((entry -> {
                        try {
                            gen.writeFieldName(entry.getKey());
                            gen.writeRawValue(entry.getValue());
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    }));
        } else {
            super.serializeFields(bean, gen, provider);
        }

    }

}

Таким образом, мы можем обработать случай, когда исходный объект использует аннотации Джексона для пользовательского поведения сериализации.

0

Мне тоже нужна была эта способность; в моем случае для поддержки расширения поля на REST-сервисах. Я закончил разработку крошечной платформы для решения этой проблемы, и этос открытым исходным кодом наGitHub, Это'также доступны вmaven центральное хранилище.

Он берет на себя всю работу. Просто оберните POJO в MorphedResult, а затем добавьте или удалите свойства по желанию. После сериализации оболочка MorphedResult исчезает иизменения появляются в сериализованном объекте JSON.

MorphedResult result = new MorphedResult<>(pojo);
result.addExpansionData("my_extra_field", "some data");

Смотрите страницу GitHub для более подробной информации и примеров. Обязательно зарегистрируйте библиотекифильтр' с ДжексономS объект сопоставления, например, так:

ObjectMapper mapper = new ObjectMapper();
mapper.setFilters(new FilteredResultProvider());
20

Джексон 2.5 представил@JsonAppend аннотация, которую можно использовать для добавления "виртуальный» свойства при сериализации. Его можно использовать с функцией mixin, чтобы избежать изменения оригинального POJO.

В следующем примере добавляетсяApprovalState свойство при сериализации:

@JsonAppend(
    attrs = {
        @JsonAppend.Attr(value = "ApprovalState")
    }
)
public static class ApprovalMixin {}

Зарегистрируйте миксин с помощью:ObjectMapper

mapper.addMixIn(POJO.class, ApprovalMixin.class);

ИспользуйтеObjectWriter установить атрибут во время сериализации:

ObjectWriter writer = mapper.writerFor(POJO.class)
                          .withAttribute("ApprovalState", "Pending");

Использование писателя для сериализации добавитApprovalState поле до выхода.

Это фактический ответ на вопрос. Похоже, Джексон-способ сделать это! Thomas Eizinger
Это решает конкретный аспект проблемы, но не сам вопрос. В моем случае мне нужно обернуть объект значением переменной, что означает, что аннотация выигранат работа. (Что-то похожее на OP 'Пример кода, напримерstartObject(); ,fieldName(myVariable);allFields(obj); ,endObject();endObject(); это то, что мне нужно). Matt
1

Другое и, пожалуй, самое простое решение:

Сделайте сериализацию двухэтапным процессом. Сначала создайтеMap лайк:

Map map = req.mapper().convertValue( result, new TypeReference>() {} );

затем добавьте свойства, которые вы хотите, как:

map.put( "custom", "value" );

затем сериализовать это в JSON:

String json = req.mapper().writeValueAsString( map );
1

Вдохновленный тем, что Вайда сказал и написал в этомсуть:

Вот как добавить слушателя для сериализации бина в Джексоне 1.9.12. В этом примере рассылка рассматривается как цепочка команд, интерфейс которой:

public interface BeanSerializerListener {
    void postSerialization(Object value, JsonGenerator jgen) throws IOException;
}

MyBeanSerializer.java:

public class MyBeanSerializer extends BeanSerializerBase {
    private final BeanSerializerListener serializerListener;

    protected MyBeanSerializer(final BeanSerializerBase src, final BeanSerializerListener serializerListener) {
        super(src);
        this.serializerListener = serializerListener;
    }

    @Override
    public void serialize(final Object bean, final JsonGenerator jgen, final SerializerProvider provider) throws IOException, JsonGenerationException {
        jgen.writeStartObject();
        if (_propertyFilterId != null) {
            serializeFieldsFiltered(bean, jgen, provider);
        } else {
            serializeFields(bean, jgen, provider);
        }

        serializerListener.postSerialization(bean, jgen);

        jgen.writeEndObject();
    }
}

MyBeanSerializerBuilder.java:

public class MyBeanSerializerBuilder extends BeanSerializerBuilder {
    private final BeanSerializerListener serializerListener;

    public MyBeanSerializerBuilder(final BasicBeanDescription beanDesc, final BeanSerializerListener serializerListener) {
        super(beanDesc);
        this.serializerListener = serializerListener;
    }

    @Override
    public JsonSerializer build() {
        BeanSerializerBase src = (BeanSerializerBase) super.build();
        return new MyBeanSerializer(src, serializerListener);
    }
}

MyBeanSerializerFactory.java:

public class MyBeanSerializerFactory extends BeanSerializerFactory {

    private final BeanSerializerListener serializerListener;

    public MyBeanSerializerFactory(final BeanSerializerListener serializerListener) {
        super(null);
        this.serializerListener = serializerListener;
    }

    @Override
    protected BeanSerializerBuilder constructBeanSerializerBuilder(final BasicBeanDescription beanDesc) {
        return new MyBeanSerializerBuilder(beanDesc, serializerListener);
    }
}

Последний класс ниже показывает, как предоставить его с помощью Resteasy 3.0.7:

@Provider
public class ObjectMapperProvider implements ContextResolver {
    private final MapperConfigurator mapperCfg;

    public ObjectMapperProvider() {
        mapperCfg = new MapperConfigurator(null, null);
        mapperCfg.setAnnotationsToUse(new Annotations[]{Annotations.JACKSON, Annotations.JAXB});
        mapperCfg.getConfiguredMapper().setSerializerFactory(serializerFactory);
    }

    @Override
    public ObjectMapper getContext(final Class type) {
        return mapperCfg.getConfiguredMapper();
    }
}
35

Поскольку (я думаю) Джексон 1.7 вы можете сделать это сBeanSerializerModifier и расширениеBeanSerializerBase, Я'Мы протестировали пример ниже с Джексоном 2.0.4.

import java.io.IOException;

import org.junit.Test;

import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.fasterxml.jackson.databind.ser.impl.ObjectIdWriter;
import com.fasterxml.jackson.databind.ser.std.BeanSerializerBase;


public class JacksonSerializeWithExtraField {

    @Test
    public void testAddExtraField() throws Exception
    {
        ObjectMapper mapper = new ObjectMapper();

        mapper.registerModule(new SimpleModule() {

            public void setupModule(SetupContext context) {
                super.setupModule(context);

                context.addBeanSerializerModifier(new BeanSerializerModifier() {

                    public JsonSerializer modifySerializer(
                            SerializationConfig config,
                            BeanDescription beanDesc,
                            JsonSerializer serializer) {
                        if (serializer instanceof BeanSerializerBase) { 
                              return new ExtraFieldSerializer(
                                   (BeanSerializerBase) serializer);
                        } 
                        return serializer; 

                    }                   
                });
            }           
        });

        mapper.writeValue(System.out, new MyClass());       
        //prints {"classField":"classFieldValue","extraField":"extraFieldValue"}
    }


    class MyClass {

        private String classField = "classFieldValue";

        public String getClassField() { 
            return classField; 
        }
        public void setClassField(String classField) { 
            this.classField = classField; 
        }
    }


    class ExtraFieldSerializer extends BeanSerializerBase {

        ExtraFieldSerializer(BeanSerializerBase source) {
            super(source);
        }

        ExtraFieldSerializer(ExtraFieldSerializer source, 
                ObjectIdWriter objectIdWriter) {
            super(source, objectIdWriter);
        }

        ExtraFieldSerializer(ExtraFieldSerializer source, 
                String[] toIgnore) {
            super(source, toIgnore);
        }

        protected BeanSerializerBase withObjectIdWriter(
                ObjectIdWriter objectIdWriter) {
            return new ExtraFieldSerializer(this, objectIdWriter);
        }

        protected BeanSerializerBase withIgnorals(String[] toIgnore) {
            return new ExtraFieldSerializer(this, toIgnore);
        }

        public void serialize(Object bean, JsonGenerator jgen,
                SerializerProvider provider) throws IOException,
                JsonGenerationException {           
            jgen.writeStartObject();                        
            serializeFields(bean, jgen, provider);
            jgen.writeStringField("extraField", "extraFieldValue"); 
            jgen.writeEndObject();
        }
    }
}
подтвердил, что работает с 2.0.4. В 2.4.1 есть два новых абстрактных метода для реализации (просто скопируйте из BeanSerializer), а modifySerializer также получает StringSerializer, который не может быть приведен. Поэтому вы должны выполнить проверку экземпляра перед тем, как привести к BeanSerializerBase dschulten
Я думаю, что люди должны знать овиртуальная собственность " Особенность в Джексоне появилась с 2.5. Эта функция была объяснена вответ ниже Timur Milovanov
10

Хотя на этот вопрос уже дан ответ, я нашел другой способ, который не требует специальных крючков Джексона.

static class JsonWrapper {
    @JsonUnwrapped
    private T inner;
    private String extraField;

    public JsonWrapper(T inner, String field) {
        this.inner = inner;
        this.extraField = field;
    }

    public T getInner() {
        return inner;
    }

    public String getExtraField() {
        return extraField;
    }
}

static class BaseClass {
    private String baseField;

    public BaseClass(String baseField) {
        this.baseField = baseField;
    }

    public String getBaseField() {
        return baseField;
    }
}

public static void main(String[] args) throws JsonProcessingException {
    Object input = new JsonWrapper<>(new BaseClass("inner"), "outer");
    System.out.println(new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(input));
}

Выходы:

{
  "baseField" : "inner",
  "extraField" : "outer"
}

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

public static void main(String[] args) throws JsonProcessingException {
    List inputs = Arrays.asList(new BaseClass("1"), new BaseClass("2"));
    //Google Guava Library <3
    List> modInputs = Lists.transform(inputs, base -> new JsonWrapper<>(base, "hello"));
    System.out.println(new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(modInputs));
}

Выход:

[ {
  "baseField" : "1",
  "extraField" : "hello"
}, {
  "baseField" : "2",
  "extraField" : "hello"
} ]
При использовании Kotlin, аннотируйте свойство, чтобы развернуть, используя.@get:JsonUnwrapped Yoni Gibbs
Это лучший ответ - не надонельзя забывать о моделях представлений только потому, что в них участвует JSON. Matthew Read
-1

Посмотрев больше наДжексон Исходный код я пришел к выводу, что этопросто невозможно достичь без написания моего собственного,BeanSerializerBeanSerializerBuilder а такжеBeanSerializerFactory и предоставить некоторые точки расширения, такие как:

/*
/**********************************************************
/* Extension points
/**********************************************************
 */

protected void beforeEndObject(T bean, JsonGenerator jgen, SerializerProvider provider) throws IOException, JSONException {
    // May be overridden
}

protected void afterStartObject(T bean, JsonGenerator jgen, SerializerProvider provider) throws IOException, JSONException {
    // May be overridden
}

К сожалению, мне пришлось скопировать и вставить весьДжексонBeanSerializer исходный кодMyCustomBeanSerializer потому что первый не разработан для расширений, объявляющих все поля и некоторые важные методы (например,serialize(...)) какfinal

@ Да, я бы неНе спешите с выводами там. Если разработчик решит сделать финал класса, это может быть хорошо продуманным решением. Открытие классов для продления - это решение, которое не должноне может быть сделано легко. whiskeysierra
Неправильное использование финала для код-нацистов. Часто я сталкивался с этим, не имея возможности расширить существующий код, просто из-за финальных методов или классов. И не надоСпорить с производительностью:stackoverflow.com/questions/4279420/... Dag
1

Мы можем использовать отражение, чтобы получить все поля объекта, который вы хотите проанализировать.

@JsonSerialize(using=CustomSerializer.class)
class Test{
  int id;
  String name;
  String hash;
}    

В пользовательском сериализаторе у нас есть наш метод сериализации, подобный этому:

        @Override
        public void serialize(Test value, JsonGenerator jgen,
                SerializerProvider provider) throws IOException,
                JsonProcessingException {

            jgen.writeStartObject();
            Field[] fields = value.getClass().getDeclaredFields();

            for (Field field : fields) {
                try {
                    jgen.writeObjectField(field.getName(), field.get(value));
                } catch (IllegalArgumentException | IllegalAccessException e) {
                    e.printStackTrace();
                }

            }
            jgen.writeObjectField("extra_field", "whatever_value");
            jgen.writeEndObject();

        }
Что делать, если вы используете @JsonProperty (value = "someOtherName») или @JsonIgnore в вашем доменном объекте? С отражением вы переопределяете существующие возможности Джексона. Это некажется хорошим. tugcem
Field класс приходит изimport java.lang.reflect.Field; gregn3
12

Вы можете сделать это (предыдущая версия не работала с Джексоном после 2.6, но это работает с Джексоном 2.7.3):

public static class CustomModule extends SimpleModule {
    public CustomModule() {
        addSerializer(CustomClass.class, new CustomClassSerializer());
    }

    private static class CustomClassSerializer extends JsonSerializer {
        @Override
        public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
            //Validate.isInstanceOf(CustomClass.class, value);
            jgen.writeStartObject();
            JavaType javaType = provider.constructType(CustomClass.class);
            BeanDescription beanDesc = provider.getConfig().introspect(javaType);
            JsonSerializer serializer = BeanSerializerFactory.instance.findBeanSerializer(provider,
                    javaType,
                    beanDesc);
            // this is basically your 'writeAllFields()'-method:
            serializer.unwrappingSerializer(null).serialize(value, jgen, provider);
            jgen.writeObjectField("my_extra_field", "some data");
            jgen.writeEndObject();
        }
    }
}



@DavidA: Код был нарушен с помощью Jackson 2.5, но я добавил исправленную версию, которая работает с Jackson 2.7. Rasmus Faber
У меня та же проблема, она входит в бесконечный цикл с последним кодом Джексона. user1595858
Спасибо @Rasmus, я ценю это! DavidA
@LasseJacobs Я попробовал это с Джексоном 2.9.0 и 2.9.6, и это работало как ожидалось с обоими. Возможно, попробуйте это:jdoodle.com/a/z99 (запустить его локально - jdoodle, видимо, можетне справиться с Джексоном). Rasmus Faber
2

Для моего случая использования я мог бы использовать намного более простой способ. В базовом классе у меня все "Джексон Пожос " Я добавить:

protected Map dynamicProperties = new HashMap();

...


public Object get(String name) {
    return dynamicProperties.get(name);
}

// "any getter" needed for serialization    
@JsonAnyGetter
public Map any() {
    return dynamicProperties;
}

@JsonAnySetter
public void set(String name, Object value) {
    dynamicProperties.put(name, value);
}

Теперь я могу десериализовать в Pojo, работать с полями и повторной сериализации без потери каких-либо свойств. Я также могу добавить / изменить не pojo свойства:

// Pojo fields
person.setFirstName("Annna");

// Dynamic field
person.set("ex", "test");

(Получил отCowtowncoder)

Похожие вопросы