[SPRING] POST / PUT 중에 모든 객체에 대한 단일 사용자 정의 디시리얼라이저 또는 해당 객체로 삽입 된 전체 객체
SPRINGPOST / PUT 중에 모든 객체에 대한 단일 사용자 정의 디시리얼라이저 또는 해당 객체로 삽입 된 전체 객체
@Entity
public Product {
@Id
public int id;
public String name;
@ManyToOne(cascade = {CascadeType.DETACH} )
Category category
@ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.DETACH} )
Set<Category> secondaryCategories;
}
이 엔티티 :
@Entity
public Category {
@Id
public int id;
public String name;
}
json과 함께 POST를 보내고 싶습니다.
{name : "name", category : 2, secondaryCategories : [3,4,5]} 클라이언트 쪽에서
다음과 같이 deserialize 할 수 있습니다.
{ name: "name", category: {id: 2 }, secondaryCategories: [{id: 3}, {id: 4}, {id: 5}] }
송신 된 경우
{ name: "name", category: {id: 2 }, secondaryCategories: [{id: 3}, {id: 4}, {id: 5}] }
나는 지금도 여전히 일하고 싶다.
어떤 종류의 주석과 맞춤 디시리얼라이저가 필요한가요? deserializer는 ID를 속성으로 가질 수있는 모든 가능한 객체에 대해 작업 할 수 있기를 바랍니다.
감사!
편집하다
해결법
-
==============================
1.또 다른 방법은 엔티티를 수정할 수있는 경우 @JsonCreator 팩토리 메서드를 사용하는 것입니다.
또 다른 방법은 엔티티를 수정할 수있는 경우 @JsonCreator 팩토리 메서드를 사용하는 것입니다.
private class Product { @JsonProperty("category") private Category category; @JsonProperty("secondaryCategories") private List<Category> secondaryCategories; } private class Category { @JsonProperty("id") private int id; @JsonCreator public static Category factory(int id){ Category p = new Category(); p.id = id; // or some db call return p; } }
또는 이와 유사한 것조차도 작동해야합니다.
private class Category { private int id; public Category() {} @JsonCreator public Category(int id) { this.id = id; } }
-
==============================
2.시도 할 수있는 몇 가지 옵션이 있습니다. 실제로 사용자 정의 디시리얼라이저 / 시리얼 라이저가 적합 할 수 있지만, @JsonIdentityInfo (역 직렬화 용) + @JsonIdentityReference (정수로 직렬화해야하는 경우) 주석을 사용하여이 작업을 수행 할 수도 있습니다.
시도 할 수있는 몇 가지 옵션이 있습니다. 실제로 사용자 정의 디시리얼라이저 / 시리얼 라이저가 적합 할 수 있지만, @JsonIdentityInfo (역 직렬화 용) + @JsonIdentityReference (정수로 직렬화해야하는 경우) 주석을 사용하여이 작업을 수행 할 수도 있습니다.
Work both for { "category":1 } { "category":{ "id":1 }
그래서 당신은 @JsonIdentityInfo로 ID에서 직렬화 될 수있는 모든 클래스에 주석을 달 필요가있다.
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = Product.class, // different for each class resolver = MyObjectIdResolver.class)
여기서 어려운 부분은 실제로 db / other 소스의 객체를 확인할 수있는 사용자 정의 ObjectIdResolver를 작성해야한다는 것입니다. 아래 예제에서 MyObjectIdResolver.resolveId 메서드의 간단한 리플렉션 버전을 살펴보십시오.
private static class MyObjectIdResolver implements ObjectIdResolver { private Map<ObjectIdGenerator.IdKey,Object> _items = new HashMap<>(); @Override public void bindItem(ObjectIdGenerator.IdKey id, Object pojo) { if (!_items.containsKey(id)) _items.put(id, pojo); } @Override public Object resolveId(ObjectIdGenerator.IdKey id) { Object object = _items.get(id); return object == null ? getById(id) : object; } protected Object getById(ObjectIdGenerator.IdKey id){ Object object = null; try { // todo objectRepository.getById(idKey.key, idKey.scope) object = id.scope.getConstructor().newInstance(); // create instance id.scope.getField("id").set(object, id.key); // set id bindItem(id, object); } catch (Exception e) { e.printStackTrace(); } return object; } @Override public ObjectIdResolver newForDeserialization(Object context) { return this; } @Override public boolean canUseFor(ObjectIdResolver resolverType) { return resolverType.getClass() == getClass(); } }
Default behavior { "category":{ "id":1 , "name":null} , secondaryCategories: [1 , { { "id":2 , "name":null} ]}
기본 동작은 다음에서 설명합니다. https://github.com/FasterXML/jackson-databind/issues/372 첫 번째 요소에 대한 객체와 각 요소에 대한 ID를 생성합니다. Jackson의 ID / 참조 메커니즘은 객체 인스턴스가 한 번만 완전히 일련 화되고 ID로 다른 곳에서 참조되도록 작동합니다.
옵션 1 (항상 ID로)
Works for { "category":1 , secondaryCategories:[1 , 2]}
각 객체 필드 위에 @JsonIdentityReference (alwaysAsId = true)를 사용해야합니다 (페이지 하단의 데모에서 주석을 제거 할 수 있음)
옵션 2 (항상 완전한 객체 표현으로)
Works for { "category" : { "id":1 , "name":null} , secondaryCategories: [{ "id":1 , "name":null} , { "id":2 , "name":null}]}
이 옵션은 직렬화를 위해 모든 IdentityInfo를 어떻게 든 제거해야하기 때문에 까다로운 작업입니다. 하나의 옵션은 2 개의 객체 매퍼를 가질 수 있습니다. 직렬화를 위해서는 1을, 직렬화를 위해서는 2를, 믹스 인이나 @JsonView를 구성하십시오.
더 쉽게 구현할 수있는 또 다른 접근법은 SerializationConfig를 사용하여 @JsonIdentityInfo 주석을 완전히 무시하는 것입니다
@Bean public ObjectMapper objectMapper() { ObjectMapper mapper = new ObjectMapper(); SerializationConfig config = mapper.getSerializationConfig() .with(new JacksonAnnotationIntrospector() { @Override public ObjectIdInfo findObjectIdInfo(final Annotated ann) { return null; } }); mapper.setConfig(config); return mapper; }
아마도 더 나은 접근법은 실제로 같은 방식으로 deserializerconfig에 대한 @JsonIdentityInfo를 정의하고 클래스 위에있는 모든 주석을 제거하는 것입니다. 이 같은
이 시점에서 사용자 정의 serializer / deserializer
여기에 일하고있다 (봄이없는 간단한 Jackson) 데모 :
import com.fasterxml.jackson.annotation.*; import com.fasterxml.jackson.databind.ObjectMapper; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Set; public class Main { @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", resolver = MyObjectIdResolver.class, scope = Category.class) public static class Category { @JsonProperty("id") public int id; @JsonProperty("name") public String name; public int getId() { return id; } public void setId(int id) { this.id = id; } @Override public String toString() { return "Category{" + "id=" + id + ", name='" + name + '\'' + '}'; } } @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", resolver = MyObjectIdResolver.class, scope = Product.class) public static class Product { @JsonProperty("id") public int id; @JsonProperty("name") public String name; // Need @JsonIdentityReference only if you want the serialization // @JsonIdentityReference(alwaysAsId = true) @JsonProperty("category") Category category; // Need @JsonIdentityReference only if you want the serialization // @JsonIdentityReference(alwaysAsId = true) @JsonProperty("secondaryCategories") Set<Category> secondaryCategories; public int getId() { return id; } public void setId(int id) { this.id = id; } @Override public String toString() { return "Product{" + "id=" + id + ", name='" + name + '\'' + ", category=" + category + ", secondaryCategories=" + secondaryCategories + '}'; } } private static class MyObjectIdResolver implements ObjectIdResolver { private Map<ObjectIdGenerator.IdKey,Object> _items; @Override public void bindItem(ObjectIdGenerator.IdKey id, Object pojo) { if (_items == null) { _items = new HashMap<ObjectIdGenerator.IdKey,Object>(); } if (!_items.containsKey(id)) _items.put(id, pojo); } @Override public Object resolveId(ObjectIdGenerator.IdKey id) { Object object = (_items == null) ? null : _items.get(id); if (object == null) { try { // create instance Constructor<?> ctor = id.scope.getConstructor(); object = ctor.newInstance(); // set id Method setId = id.scope.getDeclaredMethod("setId", int.class); setId.invoke(object, id.key); // https://github.com/FasterXML/jackson-databind/issues/372 // bindItem(id, object); results in strange behavior } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) { e.printStackTrace(); } } return object; } @Override public ObjectIdResolver newForDeserialization(Object context) { return new MyObjectIdResolver(); } @Override public boolean canUseFor(ObjectIdResolver resolverType) { return resolverType.getClass() == getClass(); } } public static void main(String[] args) throws Exception { String str = "{ \"name\": \"name\", \"category\": {\"id\": 2 }, " + "\"secondaryCategories\":[{\"id\":3},{\"id\":4},{\"id\":5}]}"; // from str Product product = new ObjectMapper().readValue(str, Product.class); System.out.println(product); // to json String productStr = new ObjectMapper().writeValueAsString(product); System.out.println(productStr); String str2 = "{ \"name\": \"name\", \"category\": 2, " + "\"secondaryCategories\": [ 3, 4, 5] }"; // from str2 Product product2 = new ObjectMapper().readValue(str2, Product.class); System.out.println(product2); // to json String productStr2 = new ObjectMapper().writeValueAsString(product2); System.out.println(productStr2); } }
-
==============================
3.많은 어려움을 겪은 후 완벽한 해결책은 https://stackoverflow.com/users/1032167/varren의 의견과 https://stackoverflow.com/a/16825934/986160 덕분이었습니다. (로컬을 통해 기본 직렬화를 사용할 수있었습니다. new objectMapper) 내 대답을 방해하지 않고 내 StdDeserializer에서 : https://stackoverflow.com/a/18405958/986160
많은 어려움을 겪은 후 완벽한 해결책은 https://stackoverflow.com/users/1032167/varren의 의견과 https://stackoverflow.com/a/16825934/986160 덕분이었습니다. (로컬을 통해 기본 직렬화를 사용할 수있었습니다. new objectMapper) 내 대답을 방해하지 않고 내 StdDeserializer에서 : https://stackoverflow.com/a/18405958/986160
코드는 int를 구문 분석하려고 시도하고 전체 객체 인 경우이를 전달합니다. 따라서 Category의 POST / PUT 요청을 만들거나 Category가 포함되지 않은 경우에는 여전히 작동합니다.
import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import org.springframework.context.annotation.Bean; import java.io.IOException; public class IdWrapperDeserializer<T> extends StdDeserializer<T> { private Class<T> clazz; public IdWrapperDeserializer(Class<T> clazz) { super(clazz); this.clazz = clazz; } @Bean public ObjectMapper objectMapper() { ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true); mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); return mapper; } @Override public T deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException { String json = jp.readValueAsTree().toString(); T obj = null; int id = 0; try { id = Integer.parseInt(json); } catch( Exception e) { obj = objectMapper().readValue(json, clazz); return obj; } try { obj = clazz.newInstance(); ReflectionUtils.set(obj,"id",id); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return obj; } }
설명 된대로 동작해야하는 각 엔티티에 대해 Spring Boot 응용 프로그램의 전역 ObjectMapper Bean에 구성해야합니다.
@Bean public ObjectMapper objectMapper() { ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true); mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); SimpleModule testModule = new SimpleModule("MyModule") .addDeserializer(Category.class, new IdWrapperDeserializer(Category.class)) mapper.registerModule(testModule); return mapper; }
이것은 https://stackoverflow.com/a/14374995/986160의 ReflectionUtils입니다.
public class ReflectionUtils { // public static boolean set(Object object, String fieldName, Object fieldValue) { Class<?> clazz = object.getClass(); while (clazz != null) { try { Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true); field.set(object, fieldValue); return true; } catch (NoSuchFieldException e) { clazz = clazz.getSuperclass(); } catch (Exception e) { throw new IllegalStateException(e); } } return false; } }
from https://stackoverflow.com/questions/46603075/single-custom-deserializer-for-all-objects-as-their-ids-or-embedded-whole-object by cc-by-sa and MIT license
'SPRING' 카테고리의 다른 글
[SPRING] Intellij는 스프링 부팅으로 hql을 올바르게 구성하는 방법을 설명합니다. 이제 Persistence QL 쿼리에 오류가 발생합니다. (0) | 2019.02.20 |
---|---|
[SPRING] 스프링 데이터 JPA - 계산 된 속성을 정렬 할 수 있습니까? (0) | 2019.02.20 |
[SPRING] @RequestScoped bean의 데이터는 여러 브라우저에서 공유됩니다. (0) | 2019.02.20 |
[SPRING] 봄 데이터의 날짜 별 제한 사항 JPA (0) | 2019.02.20 |
[SPRING] 봄철 시작 이벤트 후 Tomcat (0) | 2019.02.20 |