it-swarm-eu.dev

Jackson: Jak přidat vlastní vlastnost do JSONu bez úpravy POJO

Vyvíjím rozhraní REST pro aplikaci používající Jackson k serializaci mých objektů POJO domény na reprezentaci JSON. Chci přizpůsobit serializaci pro některé typy tak, aby přidávala další vlastnosti k reprezentaci JSON, které v POJO neexistují (např. Přidejte nějaká metadata, referenční data atd.). Vím, jak psát vlastní JsonSerializer, ale v tom případě budu muset explicitně zavolat JsonGenerator.writeXXX(..) metody pro každý vlastnost mého objektu, zatímco vše, co potřebuji, je jen přidat další vlastnost. Jinými slovy bych chtěl napsat něco jako:

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

nebo (ještě lépe) zachytit serializaci před voláním jgen.writeEndObject(), např .:

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

Myslel jsem, že můžu rozšířit BeanSerializer a přepsat jeho serialize(..) metodu, ale je to deklarováno final a také jsem nemohl najít jednoduchý způsob, jak vytvořit novou instanci BeanSerializer, aniž by jí poskytl všechny detaily metadat typu, které prakticky kopírují dobrou část Jacksona. Tak jsem to vzdal.

Moje otázka je - jak přizpůsobit Jacksonovu serializaci tak, aby přidala další věci do výstupu JSONu pro konkrétní POJOs, aniž by zavedla příliš mnoho kódů kotlových desek a znovu použila co nejvíce výchozího chování Jacksona.

47
Alex Vayda

Protože (myslím) Jackson 1.7, můžete to udělat s BeanSerializerModifier a rozšířením BeanSerializerBase. Testoval jsem příklad níže s Jacksonem 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();
        }
    }
}
35
ryanp

Jackson 2.5 představil @JsonAppend annotation, který může být používán přidat “virtuální” vlastnosti během serializace. Může být použit s funkcí mixin, aby nedošlo k úpravě původního POJO.

Následující příklad přidá vlastnost ApprovalState během serializace:

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

Zaregistrujte mixin s ObjectMapper:

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

Pomocí atributu ObjectWriter nastavte atribut během serializace:

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

Použití zapisovače pro serializaci přidá pole ApprovalState do výstupu.

Můžete to udělat (předchozí verze nefungovala s Jacksonem po 2.6, ale to funguje s Jacksonem 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<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

I když je tato otázka již zodpovězena, našel jsem jiný způsob, který nevyžaduje žádné speciální háčky na Jackson.

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

Výstupy:

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

Pro psaní sbírek můžete jednoduše použít zobrazení:

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

Výstup:

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

Pro můj případ použití bych mohl použít mnohem jednodušší způsob. V základní třídě jsem pro všechny své "Jackson Pojos" přidám:

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

Nyní mohu deserializovat na Pojo, pracovat s poli a reserialize witjout ztráty všech vlastností. Mohu také přidat/změnit vlastnosti, které nejsou pojeny:

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

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

(Mám to z Cowtowncoder )

2
Brimstedt

Inspirován z toho, co wajda řekl a napsal v tomto Gist :

Zde je návod, jak přidat posluchače pro serializaci fazolí v jacksonu 1.9.12. V tomto příkladu je listerner považován za řetězec Command of Command, jehož rozhraní je:

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

Poslední třída níže ukazuje, jak ji poskytnout pomocí Resteasy 3.0.7:

@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

Další a možná nejjednodušší řešení:

Proveďte serializaci 2-krokovým procesem. Nejprve vytvořte soubor Map<String,Object> jako:

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

pak přidejte požadované vlastnosti:

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

pak sériově toto na json:

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

Můžeme použít reflexe, abychom získali všechna pole objektu, který chcete analyzovat.

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

Ve vlastním serializátoru máme tuto metodu serializace takto: 

        @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

Můžeme rozšířit BeanSerializer, ale s malým trikem.

Nejdříve definujte třídu Java, kterou chcete zabalit.

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

}

Pak implementujte svůj vlastní 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);
        }

    }

}

Tímto způsobem můžeme zpracovat případ, že objekt Origin používá anotace jacksonu k vlastnímu serializačnímu chování.

1
smartwjw

Potřeboval jsem také tuto schopnost; v mém případě na podporu rozšiřování pole na služby REST. Nakonec jsem vytvořil malý rámec, který tento problém vyřeší, a je otevřený z github . Je také k dispozici v maven central repository .

Postará se o veškerou práci. Jednoduše zabalte POJO do MorphedResult a přidejte nebo odeberte vlastnosti podle libosti. Při serializaci zmizí obálka MorphedResult a všechny změny se objeví v serializovaném objektu JSON.

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

Další podrobnosti a příklady naleznete na stránce github. Nezapomeňte zaregistrovat knihovnu „filter“ s mapovačem objektu Jackson:

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