Jacksonを使用してPOJOドメインオブジェクトをJSON表現にシリアル化するアプリ用のRESTインターフェイスを開発しています。一部のタイプのシリアル化をカスタマイズして、追加しないプロパティをJSON表現に追加したいPOJOに存在します(たとえば、メタデータ、参照データなどを追加します。)独自のJsonSerializer
の記述方法は知っていますが、その場合はJsonGenerator.writeXXX(..)
メソッドを明示的に呼び出す必要があります- 各私のオブジェクトのプロパティが必要なのは、add追加のプロパティだけです。言い換えると、次のようなものを書きたいと思います。
_@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();
}
_
または(さらに良い)jgen.writeEndObject()
呼び出しの前に何らかの方法でシリアル化をインターセプトします。例:
_@Override void beforeEndObject(....) {
jgen.writeObjectField("my_extra_field", "some data");
}
_
BeanSerializer
を拡張してそのserialize(..)
メソッドをオーバーライドできると思っていましたが、final
と宣言されていて、BeanSerializer
すべてのタイプメタデータの詳細を提供せずに、ジャクソンのかなりの部分を実際に複製します。だから私はそれをやめることをあきらめました。
私の質問は-過剰な定型コードを導入せずに、デフォルトのJacksonの振る舞いを可能な限り再利用せずに、Jacksonのシリアル化をカスタマイズして特定のPOJOのJSON出力に追加のものを追加する方法です。
Jackson 1.7以降、BeanSerializerModifier
とBeanSerializerBase
を拡張してこれを行うことができます。 Jackson 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.5は @JsonAppend
注釈。シリアル化中に「仮想」プロパティを追加するために使用できます。元のPOJOの変更を回避するために、mixin機能とともに使用できます。
次の例では、シリアル化中に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
フィールドが出力に追加されます。
これを行うことができます(以前のバージョンは2.6以降ではJacksonで動作しませんでしたが、これはJackson 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();
}
}
}
ジャクソン2.9.0と2.9.6で試してみましたが、両方とも期待通りに機能しました。おそらくこれを試してみてください: http://jdoodle.com/a/z99 (ローカルで実行-jdoodleは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));
}
出力:
{
"baseField" : "inner",
"extraField" : "outer"
}
コレクションを作成するには、単にビューを使用できます。
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));
}
出力:
[ {
"baseField" : "1",
"extraField" : "hello"
}, {
"baseField" : "2",
"extraField" : "hello"
} ]
私のユースケースでは、もっと簡単な方法を使用できます。基本クラスでは、すべての「Jackson Pojos」に追加します:
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);
}
これで、Pojoにデシリアライズし、フィールドを操作し、プロパティを失うことなく再シリアライズできます。非pojoプロパティを追加/変更することもできます:
// Pojo fields
person.setFirstName("Annna");
// Dynamic field
person.set("ex", "test");
( Cowtowncoder )から取得
別の、おそらく最も簡単なソリューション:
シリアル化を2段階のプロセスにします。最初にMap<String,Object>
のような:
Map<String,Object> map = req.mapper().convertValue( result, new TypeReference<Map<String,Object>>() {} );
次に、必要なプロパティを追加します。
map.put( "custom", "value" );
次に、これをjsonにシリアル化します。
String json = req.mapper().writeValueAsString( map );
リフレクションを使用して、解析するオブジェクトのすべてのフィールドを取得できます。
@JsonSerialize(using=CustomSerializer.class)
class Test{
int id;
String name;
String hash;
}
カスタムシリアライザーには、次のようなserializeメソッドがあります。
@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();
}
Wajdaの発言からインスピレーションを受け、これに書かれた Gist :
Jackson 1.9.12でBeanシリアル化のリスナーを追加する方法は次のとおりです。この例では、listernerはインターフェイスのチェーンのコマンドと見なされます:
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<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();
}
}
BeanSerializer
を拡張できますが、少しのトリックがあります。
まず、Javaクラスを定義してPOJOをラップします。
@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;
}
}
次に、カスタム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);
}
}
}
このように、Jacksonアノテーションを使用してOriginオブジェクトがビヘイビアをカスタムシリアル化するケースを処理できます。
この能力も必要でした。私の場合、RESTサービスでのフィールド拡張をサポートします。この問題を解決するために小さなフレームワークを開発することになり、それは github 。 maven中央リポジトリでも利用可能です 。
すべての作業を処理します。 MorphedResultでPOJOをラップし、プロパティを自由に追加または削除します。シリアル化されると、MorphedResultラッパーが消え、シリアル化されたJSONオブジェクトに「変更」が表示されます。
MorphedResult<?> result = new MorphedResult<>(pojo);
result.addExpansionData("my_extra_field", "some data");
詳細と例については、githubページを参照してください。次のように、ジャクソンのオブジェクトマッパーにライブラリ「フィルター」を登録してください。
ObjectMapper mapper = new ObjectMapper();
mapper.setFilters(new FilteredResultProvider());