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

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과 함께해야합니다.



답변

언급했듯이 @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필수적이지 않은 메서드 (즉, 실제 직렬화 호출을 제외한 모든 것)의 표준 구현이 있으므로 확장 하는 것이 더 안전합니다 .


답변

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

}


답변

이 작업도 시도했지만 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


답변

@JsonValue 사용 :

public class User {
    int id;
    String name;

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

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


답변

사용자 지정 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;
    }
}


답변

이것은 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 직렬화에 의존 할 수 없습니다.


답변

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