Jackson과 함께 사용자 지정 Serializer를 어떻게 사용합니까?


111

Jackson을 사용하여 JSON으로 직렬화하려는 두 개의 Java 클래스가 있습니다.

public class User {
    public final int id;
    public final String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

public class Item {
    public final int id;
    public final String itemNr;
    public final User createdBy;

    public Item(int id, String itemNr, User createdBy) {
        this.id = id;
        this.itemNr = itemNr;
        this.createdBy = createdBy;
    }
}

항목을이 JSON으로 직렬화하고 싶습니다.

{"id":7, "itemNr":"TEST", "createdBy":3}

사용자가 id. 또한 다음과 같이 모든 사용자 개체를 JSON으로 serilize 할 수 있습니다.

{"id":3, "name": "Jonas", "email": "jonas@example.com"}

그래서 나는 사용자 정의 직렬 변환기를 작성해야한다고 생각하고 이것을 Item시도했습니다.

public class ItemSerializer extends JsonSerializer<Item> {

@Override
public void serialize(Item value, JsonGenerator jgen,
        SerializerProvider provider) throws IOException,
        JsonProcessingException {
    jgen.writeStartObject();
    jgen.writeNumberField("id", value.id);
    jgen.writeNumberField("itemNr", value.itemNr);
    jgen.writeNumberField("createdBy", value.user.id);
    jgen.writeEndObject();
}

}

Jackson How-to : Custom Serializers의 코드로 JSON을 직렬화합니다 .

ObjectMapper mapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule("SimpleModule", 
                                              new Version(1,0,0,null));
simpleModule.addSerializer(new ItemSerializer());
mapper.registerModule(simpleModule);
StringWriter writer = new StringWriter();
try {
    mapper.writeValue(writer, myItem);
} catch (JsonGenerationException e) {
    e.printStackTrace();
} catch (JsonMappingException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

하지만이 오류가 발생합니다.

Exception in thread "main" java.lang.IllegalArgumentException: JsonSerializer of type com.example.ItemSerializer does not define valid handledType() (use alternative registration method?)
    at org.codehaus.jackson.map.module.SimpleSerializers.addSerializer(SimpleSerializers.java:62)
    at org.codehaus.jackson.map.module.SimpleModule.addSerializer(SimpleModule.java:54)
    at com.example.JsonTest.main(JsonTest.java:54)

Jackson과 함께 사용자 지정 Serializer를 어떻게 사용할 수 있습니까?


이것이 내가 Gson을 사용하는 방법입니다.

public class UserAdapter implements JsonSerializer<User> {

    @Override 
    public JsonElement serialize(User src, java.lang.reflect.Type typeOfSrc,
            JsonSerializationContext context) {
        return new JsonPrimitive(src.id);
    }
}

    GsonBuilder builder = new GsonBuilder();
    builder.registerTypeAdapter(User.class, new UserAdapter());
    Gson gson = builder.create();
    String json = gson.toJson(myItem);
    System.out.println("JSON: "+json);

하지만 Gson은 인터페이스를 지원하지 않기 때문에 지금 Jackson과 함께해야합니다.


Jackson이 사용자 정의 Serializer를 사용하도록 어떻게 / 어디서 얻었 Item습니까? 내 컨트롤러 메서드가 표준 직렬화 된 객체를 반환하는 문제가 TypeA있지만 다른 특정 컨트롤러 메서드의 경우 다른 방식으로 직렬화하고 싶습니다. 어떤 모습일까요?
Don Cheadle 2015 년

나는 어떤 사람들에게 도움이 될 수있는 Jackson 을 사용 하여 Custom Serializer를 작성하는 방법에 대한 게시물을 썼습니다 .
Sam Berry

답변:


51

언급했듯이 @JsonValue는 좋은 방법입니다. 하지만 커스텀 시리얼 라이저가 마음에 들지 않는다면 Item 용으로 작성하는 것이 아니라 User 용으로 작성할 필요가 없습니다. 그렇다면 다음과 같이 간단합니다.

public void serialize(Item value, JsonGenerator jgen,
    SerializerProvider provider) throws IOException,
    JsonProcessingException {
  jgen.writeNumber(id);
}

또 다른 가능성은를 구현 JsonSerializable하는 것입니다.이 경우 등록이 필요하지 않습니다.

오류에 관하여; 이상합니다. 아마도 최신 버전으로 업그레이드하고 싶을 것입니다. 그러나 org.codehaus.jackson.map.ser.SerializerBase필수적이지 않은 메서드 (즉, 실제 직렬화 호출을 제외한 모든 것)의 표준 구현이 있으므로 확장 하는 것이 더 안전합니다 .


이것으로 나는 같은 오류가 발생합니다 :Exception in thread "main" java.lang.IllegalArgumentException: JsonSerializer of type com.example.JsonTest$UserSerilizer does not define valid handledType() (use alternative registration method?) at org.codehaus.jackson.map.module.SimpleSerializers.addSerializer(SimpleSerializers.java:62) at org.codehaus.jackson.map.module.SimpleModule.addSerializer(SimpleModule.java:54) at com.example.JsonTest.<init>(JsonTest.java:27) at com.exampple.JsonTest.main(JsonTest.java:102)
조나스

Jacskson의 최신 안정 버전 인 1.8.5를 사용합니다.
Jonas

4
감사. 한 번 볼게 ... 아! 실제로는 간단합니다 (오류 메시지가 좋지는 않지만)-직렬 변환기가 사용할 클래스를 지정하려면 다른 메서드로 직렬 변환기를 등록해야합니다. 그렇지 않은 경우에는 handledType ()에서 클래스를 반환해야합니다. 따라서 JavaType 또는 Class를 인수로 사용하는 'addSerializer'를 사용하면 작동합니다.
StaxMan 2011-08-24

이것이 실행되지 않으면 어떻게됩니까?
Matej J

62

@JsonSerialize(using = CustomDateSerializer.class)직렬화 할 객체의 모든 날짜 필드 위에 놓을 수 있습니다 .

public class CustomDateSerializer extends SerializerBase<Date> {

    public CustomDateSerializer() {
        super(Date.class, true);
    }

    @Override
    public void serialize(Date value, JsonGenerator jgen, SerializerProvider provider)
        throws IOException, JsonProcessingException {
        SimpleDateFormat formatter = new SimpleDateFormat("EEE MMM dd yyyy HH:mm:ss 'GMT'ZZZ (z)");
        String format = formatter.format(value);
        jgen.writeString(format);
    }

}

1
가치에 주목 : 사용 @JsonSerialize(contentUsing= ...)하면 주석 컬렉션 (예 @JsonSerialize(contentUsing= CustomDateSerializer.class) List<Date> dates)
coderatchet

33

이 작업도 시도했지만 Jackson 웹 페이지의 예제 코드에 다음 과 같이 읽어야하는 메서드 .class호출에 유형 ( ) 을 포함하지 못하는 오류가 있습니다 addSerializer().

simpleModule.addSerializer(Item.class, new ItemSerializer());

즉, 다음은 simpleModule직렬화 기를 인스턴스화 하고 추가 하는 행입니다 (이전의 잘못된 행은 주석 처리됨).

ObjectMapper mapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule("SimpleModule", 
                                          new Version(1,0,0,null));
// simpleModule.addSerializer(new ItemSerializer());
simpleModule.addSerializer(Item.class, new ItemSerializer());
mapper.registerModule(simpleModule);

참고 : 올바른 예제 코드에 대한 참조는 다음과 같습니다. http://wiki.fasterxml.com/JacksonFeatureModules


9

@JsonValue 사용 :

public class User {
    int id;
    String name;

    @JsonValue
    public int getId() {
        return id;
    }
}

@JsonValue는 메서드에서만 작동하므로 getId 메서드를 추가해야합니다. 사용자 지정 serializer를 모두 건너 뛸 수 있어야합니다.


2
이것은 사용자를 직렬화하려는 모든 시도에 영향을 미치므로 JSON을 통해 사용자 이름을 노출하기가 어렵습니다.
Paul M

또한 모든 필드가있는 모든 사용자 개체를 직렬화 할 수 있어야하므로이 솔루션을 사용할 수 없습니다. 그리고이 솔루션은 id 필드 만 포함되므로 직렬화를 중단합니다. Gson처럼 Jackson을위한 커스텀 serilizer를 만들 수있는 방법이 없나요?
Jonas

1
JSON Views (내 대답)가 귀하의 요구와 일치하지 않는 이유에 대해 의견을 주시겠습니까?
Paul M

@user : 좋은 해결책이 될 수 있습니다. 저는 그것에 대해 읽고 노력하고 있습니다.
Jonas

2
또한 @JsonSerialize (using = MySerializer.class)를 사용하여 속성 (필드 또는 게터)에 대한 특정 직렬화를 나타낼 수 있으므로 모든 유형의 인스턴스가 아닌 멤버 속성에만 사용됩니다.
StaxMan 2011-08-26

8

사용자 지정 Timestamp.class직렬화 / 역 직렬화에 대한 예제를 작성 했지만 원하는대로 사용할 수 있습니다.

객체 매퍼를 만들 때 다음과 같이하십시오.

public class JsonUtils {

    public static ObjectMapper objectMapper = null;

    static {
        objectMapper = new ObjectMapper();
        SimpleModule s = new SimpleModule();
        s.addSerializer(Timestamp.class, new TimestampSerializerTypeHandler());
        s.addDeserializer(Timestamp.class, new TimestampDeserializerTypeHandler());
        objectMapper.registerModule(s);
    };
}

예를 들어 java ee다음과 같이 초기화 할 수 있습니다.

import java.time.LocalDateTime;

import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;

@Provider
public class JacksonConfig implements ContextResolver<ObjectMapper> {

    private final ObjectMapper objectMapper;

    public JacksonConfig() {
        objectMapper = new ObjectMapper();
        SimpleModule s = new SimpleModule();
        s.addSerializer(Timestamp.class, new TimestampSerializerTypeHandler());
        s.addDeserializer(Timestamp.class, new TimestampDeserializerTypeHandler());
        objectMapper.registerModule(s);
    };

    @Override
    public ObjectMapper getContext(Class<?> type) {
        return objectMapper;
    }
}

serializer는 다음과 같아야합니다.

import java.io.IOException;
import java.sql.Timestamp;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

public class TimestampSerializerTypeHandler extends JsonSerializer<Timestamp> {

    @Override
    public void serialize(Timestamp value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
        String stringValue = value.toString();
        if(stringValue != null && !stringValue.isEmpty() && !stringValue.equals("null")) {
            jgen.writeString(stringValue);
        } else {
            jgen.writeNull();
        }
    }

    @Override
    public Class<Timestamp> handledType() {
        return Timestamp.class;
    }
}

그리고 deserializer는 다음과 같습니다.

import java.io.IOException;
import java.sql.Timestamp;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.SerializerProvider;

public class TimestampDeserializerTypeHandler extends JsonDeserializer<Timestamp> {

    @Override
    public Timestamp deserialize(JsonParser jp, DeserializationContext ds) throws IOException, JsonProcessingException {
        SqlTimestampConverter s = new SqlTimestampConverter();
        String value = jp.getValueAsString();
        if(value != null && !value.isEmpty() && !value.equals("null"))
            return (Timestamp) s.convert(Timestamp.class, value);
        return null;
    }

    @Override
    public Class<Timestamp> handledType() {
        return Timestamp.class;
    }
}

7

이것은 Jackson 직렬화를 이해하려고 시도하는 동안 발견 한 행동 패턴입니다.

1) 개체 Classroom과 Class Student가 있다고 가정합니다. 나는 쉽게 모든 것을 공개하고 최종적으로 만들었다.

public class Classroom {
    public final double double1 = 1234.5678;
    public final Double Double1 = 91011.1213;
    public final Student student1 = new Student();
}

public class Student {
    public final double double2 = 1920.2122;
    public final Double Double2 = 2324.2526;
}

2) 객체를 JSON으로 직렬화하는 데 사용하는 직렬 변환기라고 가정합니다. writeObjectField는 객체 매퍼에 등록 된 경우 객체의 자체 직렬 변환기를 사용합니다. 그렇지 않은 경우 POJO로 직렬화합니다. writeNumberField는 독점적으로 기본 요소 만 인수로 허용합니다.

public class ClassroomSerializer extends StdSerializer<Classroom> {
    public ClassroomSerializer(Class<Classroom> t) {
        super(t);
    }

    @Override
    public void serialize(Classroom value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
        jgen.writeStartObject();
        jgen.writeObjectField("double1-Object", value.double1);
        jgen.writeNumberField("double1-Number", value.double1);
        jgen.writeObjectField("Double1-Object", value.Double1);
        jgen.writeNumberField("Double1-Number", value.Double1);
        jgen.writeObjectField("student1", value.student1);
        jgen.writeEndObject();
    }
}

public class StudentSerializer extends StdSerializer<Student> {
    public StudentSerializer(Class<Student> t) {
        super(t);
    }

    @Override
    public void serialize(Student value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
        jgen.writeStartObject();
        jgen.writeObjectField("double2-Object", value.double2);
        jgen.writeNumberField("double2-Number", value.double2);
        jgen.writeObjectField("Double2-Object", value.Double2);
        jgen.writeNumberField("Double2-Number", value.Double2);
        jgen.writeEndObject();
    }
}

3) ###,##0.000SimpleModule에서 DecimalFormat 출력 패턴으로 DoubleSerializer 만 등록 하면 출력은 다음과 같습니다.

{
  "double1" : 1234.5678,
  "Double1" : {
    "value" : "91,011.121"
  },
  "student1" : {
    "double2" : 1920.2122,
    "Double2" : {
      "value" : "2,324.253"
    }
  }
}

POJO 직렬화는 Double에 DoubleSerialzer를 사용하고 Double에 일반 String 형식을 사용하여 double과 Double을 구분하는 것을 볼 수 있습니다.

4) StudentSerializer없이 DoubleSerializer 및 ClassroomSerializer를 등록합니다. 우리는 double을 객체로 쓰면 Double처럼 동작하고 Double을 숫자로 쓰면 double처럼 동작하는 출력이 나올 것으로 예상합니다. Student 인스턴스 변수는 등록하지 않으므로 POJO로 작성하고 위의 패턴을 따라야합니다.

{
  "double1-Object" : {
    "value" : "1,234.568"
  },
  "double1-Number" : 1234.5678,
  "Double1-Object" : {
    "value" : "91,011.121"
  },
  "Double1-Number" : 91011.1213,
  "student1" : {
    "double2" : 1920.2122,
    "Double2" : {
      "value" : "2,324.253"
    }
  }
}

5) 모든 시리얼 라이저를 등록합니다. 출력은 다음과 같습니다.

{
  "double1-Object" : {
    "value" : "1,234.568"
  },
  "double1-Number" : 1234.5678,
  "Double1-Object" : {
    "value" : "91,011.121"
  },
  "Double1-Number" : 91011.1213,
  "student1" : {
    "double2-Object" : {
      "value" : "1,920.212"
    },
    "double2-Number" : 1920.2122,
    "Double2-Object" : {
      "value" : "2,324.253"
    },
    "Double2-Number" : 2324.2526
  }
}

예상대로 정확히.

또 다른 중요한 참고 사항 : 동일한 모듈에 등록 된 동일한 클래스에 대해 여러 직렬 변환기가있는 경우 모듈은 가장 최근에 목록에 추가 된 해당 클래스의 직렬 변환기를 선택합니다. 사용해서는 안됩니다. 혼란스럽고 이것이 얼마나 일관성이 있는지 잘 모르겠습니다.

도덕적 : 객체에서 기본 요소의 직렬화를 사용자 정의하려면 객체에 대한 자체 직렬화를 작성해야합니다. POJO Jackson 직렬화에 의존 할 수 없습니다.


Classroom 발생 등을 처리하기 위해 ClassroomSerializer를 어떻게 등록합니까?
Trismegistos

5

Jackson의 JSON 뷰 는 특히 JSON 형식에 유연성이있는 경우 요구 사항을 달성하는 더 간단한 방법 일 수 있습니다.

경우 {"id":7, "itemNr":"TEST", "createdBy":{id:3}}, 허용 할 수있는 정도의 표시는 다음이 아주 작은 코드로 매우 달성하기 쉬운 것입니다.

User의 이름 필드에 뷰의 일부로 주석을 달고 직렬화 요청에서 다른 뷰를 지정합니다 (주석이없는 필드는 기본적으로 포함됨).

예 :보기를 정의하십시오.

public class Views {
    public static class BasicView{}
    public static class CompleteUserView{}
}

사용자에게 주석 달기 :

public class User {
    public final int id;

    @JsonView(Views.CompleteUserView.class)
    public final String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

숨기려는 필드가 포함되지 않은 뷰를 요청하는 직렬화 (주석이없는 필드는 기본적으로 직렬화 됨) :

objectMapper.getSerializationConfig().withView(Views.BasicView.class);

Jackson JSON Views는 사용하기 어렵고이 문제에 대한 좋은 해결책을 찾을 수 없습니다.
Jonas

Jonas-예를 추가했습니다. 뷰가 동일한 객체를 다른 방식으로 직렬화하는 데 정말 좋은 솔루션임을 발견했습니다.
Paul M

좋은 예를 들어 주셔서 감사합니다. 이것은 지금까지 최고의 솔루션입니다. 그러나 createdBy객체가 아닌 값 으로 얻을 수있는 방법이 없습니까?
Jonas

setSerializationView()더 이상 사용되지 않는 것 같아서 mapper.viewWriter(JacksonViews.ItemView.class).writeValue(writer, myItem);대신 사용 했습니다.
Jonas

jsonviews를 사용하여 의심합니다. 뷰를 검색하기 전에 사용한 빠르고 더러운 솔루션은 관심있는 속성을 맵에 복사 한 다음 맵을 직렬화하는 것입니다.
Paul M

5

필자의 경우 (Spring 3.2.4 및 Jackson 2.3.1) 사용자 지정 직렬 변환기에 대한 XML 구성 :

<mvc:annotation-driven>
    <mvc:message-converters register-defaults="false">
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <property name="objectMapper">
                <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
                    <property name="serializers">
                        <array>
                            <bean class="com.example.business.serializer.json.CustomObjectSerializer"/>
                        </array>
                    </property>
                </bean>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

설명 할 수없는 방식으로 무언가에 의해 기본값으로 다시 덮어 써졌습니다.

이것은 나를 위해 일했습니다.

CustomObject.java

@JsonSerialize(using = CustomObjectSerializer.class)
public class CustomObject {

    private Long value;

    public Long getValue() {
        return value;
    }

    public void setValue(Long value) {
        this.value = value;
    }
}

CustomObjectSerializer.java

public class CustomObjectSerializer extends JsonSerializer<CustomObject> {

    @Override
    public void serialize(CustomObject value, JsonGenerator jgen,
        SerializerProvider provider) throws IOException,JsonProcessingException {
        jgen.writeStartObject();
        jgen.writeNumberField("y", value.getValue());
        jgen.writeEndObject();
    }

    @Override
    public Class<CustomObject> handledType() {
        return CustomObject.class;
    }
}

<mvc:message-converters>(...)</mvc:message-converters>내 솔루션에는 XML 구성 ( )이 필요하지 않습니다.


1

사용자 지정 serializer의 유일한 요구 사항이의 name필드 직렬화를 건너 뛰는 것이라면 transientUser 로 표시하십시오 . Jackson은 일시적인 직렬화 또는 역 직렬화하지 않습니다. 필드를 .

[참조 : Java에 임시 필드가있는 이유는 무엇입니까? ]


어디에 표시합니까? - User클래스에서? 그러나 모든 사용자 개체도 직렬화합니다. 예를 들어 먼저 모두 직렬화 items( userId사용자 객체에 대한 참조 로만 사용) 한 다음 all을 직렬화합니다 users. 이 경우 User-class의 파일을 표시 할 수 없습니다 .
Jonas

이 새로운 정보를 고려할 때이 접근 방식은 효과가 없습니다. Jackson이 사용자 지정 serializer에 대한 자세한 정보를 찾고있는 것 같습니다 (handledType () 메서드를 재정의해야합니까?)
Mike G

예, 그러나 handledType()링크 된 문서 의 방법에 대한 정보 handledType()가 없으며 Eclipse가 구현하는 방법을 생성하면 no 가 생성되므로 혼란 스럽습니다.
Jonas

링크 한 위키가 그것을 참조하지 않기 때문에 확실하지 않지만 버전 1.5.1에는 handledType ()이 있고 예외가 메소드가 없거나 유효하지 않다고 불평하는 것 같습니다 (기본 클래스는 메소드에서 null을 반환합니다). jackson.codehaus.org/1.5.1/javadoc/org/codehaus/jackson/map/…
Mike G


0

귀하의 경우 문제는 ItemSerializer에 JsonSerializer에서 재정의 해야하는 handledType () 메서드가 없다는 것입니다.

    public class ItemSerializer extends JsonSerializer<Item> {

    @Override
    public void serialize(Item value, JsonGenerator jgen,
            SerializerProvider provider) throws IOException,
            JsonProcessingException {
        jgen.writeStartObject();
        jgen.writeNumberField("id", value.id);
        jgen.writeNumberField("itemNr", value.itemNr);
        jgen.writeNumberField("createdBy", value.user.id);
        jgen.writeEndObject();
    }

   @Override
   public Class<Item> handledType()
   {
    return Item.class;
   }
}

따라서 handledType () 이 정의되지 않았다는 명시 적 오류가 발생합니다.

Exception in thread "main" java.lang.IllegalArgumentException: JsonSerializer of type com.example.ItemSerializer does not define valid handledType() 

누군가에게 도움이되기를 바랍니다. 내 대답을 읽어 주셔서 감사합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.