web-gelistirme-sc.com

Jackson: POJO'yu değiştirmeden JSON'a özel özellik nasıl eklenir

POJO etki alanı nesnelerimi JSON temsiline seri hale getirmek için Jackson kullanarak uygulamam için bir REST arayüzü geliştiriyorum. POJO'larda bulunmayan JSON temsiline ek özellikler eklemek için bazı türlerde serileştirmeyi özelleştirmek istiyorum (örneğin, bazı meta veriler, referans verileri vb. Ekleyin). Kendi JsonSerializer'ımı nasıl yazacağımı biliyorum, ancak bu durumda nesnemin her özelliği için açıkça JsonGenerator.writeXXX(..) yöntemlerini çağırmam gerekirken, tek ihtiyacım olan şey sadece [ ek bir özellik eklemek. Başka bir deyişle, şöyle bir şey yazabilmek istiyorum:

@Override
public void serialize(TaxonomyNode value, JsonGenerator jgen, SerializerProvider provider) {
    jgen.writeStartObject();
    jgen.writeAllFields(value); // <-- The method I'd like to have
    jgen.writeObjectField("my_extra_field", "some data");
    jgen.writeEndObject();
}

veya (hatta daha iyisi) jgen.writeEndObject() çağrısından önce serileştirmeyi bir şekilde engellemek için;

@Override void beforeEndObject(....) {
    jgen.writeObjectField("my_extra_field", "some data");
}

BeanSerializer öğesini uzatabileceğimi ve serialize(..) yöntemini geçersiz kılabileceğimi düşündüm, ancak final olarak ilan edildi ve ayrıca Jackson'ın pratik olarak çoğaltılmasıyla tüm tip meta veri ayrıntılarını sağlamadan yeni bir BeanSerializer örneği oluşturmanın kolay bir yolunu bulamadım. Bu yüzden bunu yapmaktan vazgeçtim.

Sorum şu: - belirli POJO'lar için JSON çıkışına ek malzeme eklemek ve kazancı kodunu çok fazla kullanmadan ve varsayılan Jackson davranışını mümkün olduğunca tekrar kullanmak için Jackson'ın seri hale getirmesini özelleştirme.

47
Alex Vayda

(Bence) Jackson 1.7'den beri bunu BeanSerializerModifier ve genişletme BeanSerializerBase ile yapabilirsiniz. Aşağıdaki örneği Jackson 2.0.4 ile test ettim.

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();
        }
    }
}
35
ryanp

Jackson 2.5, serileştirme sırasında "sanal" özellikler eklemek için kullanılabilen @JsonAppend notunu başlattı. Orijinal POJO'yu değiştirmekten kaçınmak için karışım işlevi ile birlikte kullanılabilir.

Aşağıdaki örnek, serileştirme sırasında ApprovalState özelliği ekler:

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

Karışımı ObjectMapper ile kaydedin:

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

Serileştirme sırasında niteliği ayarlamak için bir ObjectWriter kullanın:

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

Serileştirme için yazıcının kullanılması, çıktıya ApprovalState alanını ekleyecektir.

20

Bunu yapabilirsiniz (önceki sürüm 2.6'dan sonra Jackson ile çalışmadı, ancak bu Jackson 2.7.3 ile çalışıyor):

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<Object> 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();
        }
    }
}
12
Rasmus Faber

Bu soru zaten cevaplanmış olsa da, özel Jackson kancaları gerektirmeyen başka bir yol buldum.

static class JsonWrapper<T> {
    @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));
}

Çıktılar:

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

Koleksiyon yazmak için basitçe bir görünüm kullanabilirsiniz:

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

Çıktı:

[ {
  "baseField" : "1",
  "extraField" : "hello"
}, {
  "baseField" : "2",
  "extraField" : "hello"
} ]
9
DieterDP

Kullanım davam için çok daha basit bir yol kullanabilirim. Temel sınıfta ekleyeceğim tüm "Jackson Pojos" lara sahibim:

protected Map<String,Object> dynamicProperties = new HashMap<String,Object>();

...


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

// "any getter" needed for serialization    
@JsonAnyGetter
public Map<String,Object> any() {
    return dynamicProperties;
}

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

Artık Pojo'ya seri hale getirebilir, alanlarla çalışabilir ve herhangi bir mülkünü kaybetme konusunda yeniden seri hale getirebilirim. Pojo olmayan özellikleri de ekleyebilir/değiştirebilirim:

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

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

( Cowtowncoder 'dan aldım)

2
Brimstedt

Wajda'nın söylediklerinden ve yazdıklarından ilham alan Gist :

1.9.12 jackson'da fasülye serileştirme için bir dinleyicinin nasıl ekleneceği aşağıda açıklanmıştır. Bu örnekte, listerner arabirimi olan Komuta Zinciri olarak kabul edilir:

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);
    }
}

Aşağıdaki son sınıf, Resteasy 3.0.7 kullanarak nasıl sağlanacağını gösterir:

@Provider
public class ObjectMapperProvider implements ContextResolver<ObjectMapper> {
    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();
    }
}
1
Charlouze

Başka ve belki de en basit çözüm:

Serileştirmeyi 2 adımlı bir işlem yapın. İlk önce şöyle bir Map<String,Object> oluşturun:

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

sonra istediğiniz özellikleri ekleyin:

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

daha sonra bunu json'a seri hale getirin:

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

Ayrıştırmak istediğiniz nesnenin tüm alanlarını elde etmek için yansıma kullanabiliriz.

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

Özel seri düzenleyicide şöyle serileştirme yöntemimiz var: 

        @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();

        }
1
Sourabh

BeanSerializer genişletebiliriz, ancak küçük bir hile ile.

İlk önce, POJO'nuzu sarmak için bir Java sınıfı tanımlayın.

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

    private final Object Origin;
    private final Map<String, String> 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<String, String> getMixed() {
        return mixed;
    }

    public Object getOrigin() {
        return Origin;
    }

}

Sonra custom özel serializer uygulayın.

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);
        }

    }

}

Bu şekilde, Origin nesnesinin, özel seri hale getirme davranışını jackson ek açıklamaları kullanarak ele almasını sağlayabiliriz.

1
smartwjw

Bu yeteneğe de ihtiyacım vardı; benim durumumda, REST hizmetlerinde alan genişlemesini desteklemek. Bu sorunu çözmek için ufak bir çerçeve geliştirdim ve bu durum github kaynaklı açık bir kaynak. Ayrıca maven merkezi deposunda bulunur.

Tüm işlerle ilgileniyor. POJO'yu bir MorphedResult içine sarın ve ardından istediğiniz zaman özellikler ekleyin veya kaldırın. Serileştirildiğinde, MorphedResult sarmalayıcısı kaybolur ve serileştirilmiş JSON nesnesinde herhangi bir 'değişiklik' belirir.

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

Daha fazla bilgi ve örnekler için github sayfasına bakınız. 'Filter' kütüphanesini Jackson'ın nesne eşleştiricisi ile kaydettiğinizden emin olun:

ObjectMapper mapper = new ObjectMapper();
mapper.setFilters(new FilteredResultProvider());
0
allenru