Jackson : POJO를 수정하지 않고 JSON에 사용자 지정 속성을 추가하는 방법
Jackson을 사용하여 POJO 도메인 개체를 JSON 표현으로 직렬화하는 내 앱용 REST 인터페이스를 개발 중입니다. POJO에 존재하지 않는 JSON 표현에 추가 속성을 추가하기 위해 일부 유형에 대한 직렬화를 사용자 정의하고 싶습니다 (예 : 일부 메타 데이터, 참조 데이터 등 추가). 내 자신의을 작성하는 방법을 알고 JsonSerializer
있지만이 경우에는 객체의 각 속성에 대해 명시 적으로 JsonGenerator.writeXXX(..)
메서드를 호출 해야하지만 필요한 것은 속성 을 추가 하는 것 뿐입니다 . 즉, 다음과 같이 쓸 수 있기를 바랍니다.
@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의 직렬화를 사용자 정의하는 방법입니다.
(내 생각에) Jackson 1.7 이후로 a 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();
}
}
}
Jackson 2.5는 @JsonAppend
직렬화 중에 "가상"속성을 추가하는 데 사용할 수있는 주석을 도입했습니다 . 원래 POJO를 수정하지 않도록 mixin 기능과 함께 사용할 수 있습니다.
다음 예제는 ApprovalState
직렬화 중에 속성을 추가합니다 .
@JsonAppend(
attrs = {
@JsonAppend.Attr(value = "ApprovalState")
}
)
public static class ApprovalMixin {}
mixin을 다음과 같이 등록하십시오 ObjectMapper
.
mapper.addMixIn(POJO.class, ApprovalMixin.class);
ObjectWriter
직렬화 중에 속성을 설정 하려면를 사용하십시오 .
ObjectWriter writer = mapper.writerFor(POJO.class)
.withAttribute("ApprovalState", "Pending");
직렬화를 위해 작성기를 사용하면 ApprovalState
출력에 필드 가 추가됩니다 .
이 질문은 이미 답변되었지만 특별한 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"
} ]
이렇게 할 수 있습니다 (이전 버전은 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();
}
}
}
최신 정보:
나는 Jackson 2.9.0과 2.9.6에서 시험해 보았고 둘 다 예상대로 작동했습니다. 아마도 이것을 시도해보십시오 : http://jdoodle.com/a/z99 (로컬에서 실행하십시오-jdoodle은 분명히 Jackson을 처리 할 수 없습니다).
제 사용 사례에서는 훨씬 더 간단한 방법을 사용할 수 있습니다. 기본 클래스에서 모든 "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로 deserialize하고, 필드로 작업하고, 속성을 잃는 witjout을 다시 직렬화 할 수 있습니다. pojo가 아닌 속성을 추가 / 변경할 수도 있습니다.
// Pojo fields
person.setFirstName("Annna");
// Dynamic field
person.set("ex", "test");
( Cowtowncoder 에서 얻었습니다 )
리플렉션을 사용하여 구문 분석하려는 개체의 모든 필드를 가져올 수 있습니다.
@JsonSerialize(using=CustomSerializer.class)
class Test{
int id;
String name;
String hash;
}
커스텀 serializer에는 다음과 같은 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();
}
아마도 가장 간단한 또 다른 해결책 :
직렬화를 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 );
wajda가이 요점 에서 말하고 쓴 내용에서 영감을 얻었습니다 .
다음은 jackson 1.9.12에서 빈 직렬화를위한 리스너를 추가하는 방법입니다. 이 예에서 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
있지만 약간의 트릭이 있습니다.
먼저 POJO를 래퍼 할 Java 클래스를 정의합니다.
@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 어노테이션을 사용하여 동작을 직렬화하는 원본 객체를 처리 할 수 있습니다.
저도이 능력이 필요했습니다. 제 경우에는 REST 서비스에서 필드 확장을 지원합니다. 이 문제를 해결하기 위해 작은 프레임 워크를 개발하게되었고 github 에서 오픈 소스되었습니다 . Maven 중앙 저장소 에서도 사용할 수 있습니다 .
모든 작업을 처리합니다. MorphedResult에서 POJO를 래핑 한 다음 원하는대로 속성을 추가하거나 제거하면됩니다. 직렬화되면 MorphedResult 래퍼가 사라지고 직렬화 된 JSON 객체에 '변경 사항'이 나타납니다.
MorphedResult<?> result = new MorphedResult<>(pojo);
result.addExpansionData("my_extra_field", "some data");
자세한 내용과 예제는 github 페이지를 참조하십시오. 다음과 같이 Jackson의 객체 매퍼에 라이브러리 '필터'를 등록해야합니다.
ObjectMapper mapper = new ObjectMapper();
mapper.setFilters(new FilteredResultProvider());
온 더 찾고 후 잭슨의 소스 코드를 나는 내 자신을 작성하지 않고 달성하기 위해 단순히 불가능하다는 결론을 내렸다 BeanSerializer
, BeanSerializerBuilder
그리고 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
'IT이야기' 카테고리의 다른 글
JAX-WS 클라이언트의 SSLContext를 프로그래밍 방식으로 설정하는 방법 (0) | 2021.03.27 |
---|---|
VB.NET에서 축 어적 문자열 리터럴을 수행하는 방법 (0) | 2021.03.27 |
Gradle : 앱에서 설정되는 플래그를 사용하여 Android 라이브러리에서 BuildConfig를 사용하는 방법 (0) | 2021.03.26 |
ImportError : concurrent.futures.process라는 모듈이 없습니다. (0) | 2021.03.26 |
React Component에서 이미지 (.svg, .png)를 가져 오는 방법 (0) | 2021.03.26 |