Angular UI와 다른 나머지 클라이언트에서 json을 사용 하는 나머지 서비스 가 있습니다. ~ 50 개의 테이블이있는 데이터베이스에 저장되는 ~ 50 개의 엔터티의 복잡한 구조를 기반으로하는 데이터입니다. 문제는 선택적 OneToOne 관계입니다. Angular는 선택적 개체를 "car": {},
. 스프링 데이터 저장소는 빈 항목으로 저장하고 Json 응답을 받았습니다 "car": {"id": 545234, "version": 0}
. 빈 개체를 무시하는 Jackson 주석이 없으며 비어 있거나 null 속성 만 있습니다.
Employee Entity의 형식은 다음과 같습니다.
@Entity
public class Employee {
@Id
@GeneratedValue
private Long id;
@Version
private Long version;
private String name;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "car_id")
@JsonManagedReference
private Car car;
.
. Desk, getters and setters
.
}
OneToOne 참조의 다른 쪽
@Entity
public class Car{
@Id
@GeneratedValue
private Long id;
@Version
private Long version;
private String name;
@OneToOne(fetch = FetchType.LAZY, mappedBy = "employee")
@JsonBackReference
private Employee employee;
.
. getters and setters
.
}
예를 들어, 나는 이것을 사후 작업으로 내 서비스에 보냅니다.
{
"name": "ACME",
.
.
.
"employee": {
"name": "worker 1",
"car": {},
"desk": {
floor: 3,
number: 4,
phone: 444
}
.
.
.
},
"addresses": [],
"building": {},
.
.
.
}
응답으로 저장된 데이터를 받았습니다.
{
"id": 34534,
"version": 0,
"name": "ACME",
.
.
.
"employee": {
"id": 34535,
"version":0,
"name": "worker 1",
"car": {"id": 34536, "version": 0},
"desk": {
"id": 34538,
"version":0,
"floor": 3,
"number": 4,
"phone": 444
}
.
.
.
},
"addresses": [],
"building": {"id": 34539, "version": 0},
.
.
.
}
응답에서 볼 수 있듯이 ID, 버전, 많은 null 값 및 빈 문자열이있는 빈 테이블 행을 얻었습니다. 주 역 직렬화 된 회사 클래스를 저장 (지속) 할 때 다른 엔터티도 저장됩니다. .
Do not include empty object to Jackson 과 같은 많은 예제를 찾았 습니다. 구체적인 pojo와 구체적인 deserializer가 작동하지만 모든 엔티티에는 자체 Deserializer가 필요합니다. 이로 인해 현재 엔터티와 향후 새 엔터티에 대해 많은 작업이 발생합니다 (선택적 엔터티 만 해당).
나는 다음을 시도했고, 나는 BeanDeserializerModifier
표준 beandeserializer를 통해 자체 deserializer를 래핑하려고 시도했습니다.
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
@Override
public List<BeanPropertyDefinition> updateProperties(DeserializationConfig config,
BeanDescription beanDesc,
List<BeanPropertyDefinition> propDefs) {
logger.debug("update properties, beandesc: {}", beanDesc.getBeanClass().getSimpleName());
return super.updateProperties(config, beanDesc, propDefs);
}
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
BeanDescription beanDesc,
JsonDeserializer<?> deserializer) {
logger.debug("modify deserializer {}",beanDesc.getBeanClass().getSimpleName());
// This fails:
// return new DeserializationWrapper(deserializer, beanDesc);
return deserializer; // This works, but it is the standard behavior
}
});
그리고 여기에 래퍼 (그리고 실수)가 있습니다.
public class DeserializationWrapper extends JsonDeserializer<Object> {
private static final Logger logger = LoggerFactory.getLogger( DeserializationWrapper.class );
private final JsonDeserializer<?> deserializer;
private final BeanDescription beanDesc;
public DeserializationWrapper(JsonDeserializer<?> deserializer, BeanDescription beanDesc) {
this.deserializer = deserializer;
this.beanDesc = beanDesc;
}
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
logger.debug("deserialize in wrapper {} ",beanDesc.getBeanClass().getSimpleName());
final Object deserialized = deserializer.deserialize(p, ctxt);
ObjectCodec codec = p.getCodec();
JsonNode node = codec.readTree(p);
// some logig that not work
// here. The Idea is to detect with the json parser that the node is empty.
// If it is empty I will return null here and not the deserialized pojo
return deserialized;
}
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt, Object intoValue) throws IOException {
logger.debug("deserializer - method 2");
intoValue = deserializer.deserialize(p, ctxt);
return intoValue;
}
@Override
public boolean isCachable() {
return deserializer.isCachable();
}
.
. I try to wrap the calls to the deserializer
.
Deserialization 래퍼가 작동하지 않고 예외가있는 첫 번째 호출 후 충돌합니다. com.fasterxml.jackson.databind.exc.MismatchedInputException: No _valueDeserializer assigned at [Source: (PushbackInputStream); line: 2, column: 11] (through reference chain: ... Company["name"])
내 질문 : deserializer가 파싱하는 동안 현재 jsonNode가 비어 있고 빈 클래스 인스턴스 대신 null을 반환하는 방식으로 작동 표준 deserializer의 동작을 확장하는 방법이 있습니까? 아마도 내 아이디어가 잘못되었고 완전히 다른 해결책이 있습니까?
Angular UI 쪽에서 해결하는 것은 옵션이 아닙니다. 우리는 Jackson 2.9.5를 사용합니다.
BeanDeserializerModifier
커스텀 디시리얼라이저와 함께 사용 하는 것이 좋습니다. 디시리얼라이저 구현 만 개선하면됩니다. 귀하의 예제에서 문제는 다음 줄에 있습니다.
final Object deserialized = deserializer.deserialize(p, ctxt); //1.
ObjectCodec codec = p.getCodec(); //2.
JsonNode node = codec.readTree(p); //3.
라인 1.
읽기 JSON Object
. 라인에서 2.
그리고 3.
당신은 만들려고 JsonNode
하지만 빈은 JSON Object
이미 줄을 읽었습니다 1.
. 다음 두 줄은 나머지 페이로드를 JsonNode
.
Jackson
기본적으로 BeanDeserializer
일반 POJO
클래스 를 역 직렬화 하는 데 사용 됩니다 . 이 클래스를 확장하고 자체 구현을 제공 할 수 있습니다. 버전 2.10.1
deserialise
방법은 다음과 같습니다.
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
// common case first
if (p.isExpectedStartObjectToken()) {
if (_vanillaProcessing) {
return vanillaDeserialize(p, ctxt, p.nextToken());
}
// 23-Sep-2015, tatu: This is wrong at some many levels, but for now... it is
// what it is, including "expected behavior".
p.nextToken();
if (_objectIdReader != null) {
return deserializeWithObjectId(p, ctxt);
}
return deserializeFromObject(p, ctxt);
}
return _deserializeOther(p, ctxt, p.getCurrentToken());
}
대부분의 경우 특별한 처리가 필요하지 않은 경우 vanillaDeserialize
메서드가 호출됩니다. 살펴 보겠습니다.
private final Object vanillaDeserialize(JsonParser p, DeserializationContext ctxt, JsonToken t) throws IOException {
final Object bean = _valueInstantiator.createUsingDefault(ctxt);
// [databind#631]: Assign current value, to be accessible by custom serializers
p.setCurrentValue(bean);
if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
String propName = p.getCurrentName();
do {
p.nextToken();
SettableBeanProperty prop = _beanProperties.find(propName);
if (prop != null) { // normal case
try {
prop.deserializeAndSet(p, ctxt, bean);
} catch (Exception e) {
wrapAndThrow(e, bean, propName, ctxt);
}
continue;
}
handleUnknownVanilla(p, ctxt, bean, propName);
} while ((propName = p.nextFieldName()) != null);
}
return bean;
}
보시다시피, 그것은 우리가 원하는 거의 모든 것을 수행합니다. 다만 비어있는 경우에도 새 객체를 생성합니다 JSON Object
. 새 개체를 생성 한 직후 필드가 존재하는지 확인합니다. 한 줄이 너무 멀습니다. 안타깝게도이 메서드는 비공개이며 재정의 할 수 없습니다. 클래스에 복사하고 약간 수정 해 보겠습니다.
class EmptyObjectIsNullBeanDeserializer extends BeanDeserializer {
EmptyObjectIsNullBeanDeserializer(BeanDeserializerBase src) {
super(src);
}
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
if (_vanillaProcessing) {
return vanillaDeserialize(p, ctxt);
}
return super.deserialize(p, ctxt);
}
private Object vanillaDeserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
p.nextToken();
if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
final Object bean = _valueInstantiator.createUsingDefault(ctxt);
// [databind#631]: Assign current value, to be accessible by custom serializers
p.setCurrentValue(bean);
String propName = p.getCurrentName();
do {
p.nextToken();
SettableBeanProperty prop = _beanProperties.find(propName);
if (prop != null) { // normal case
try {
prop.deserializeAndSet(p, ctxt, bean);
} catch (Exception e) {
wrapAndThrow(e, bean, propName, ctxt);
}
continue;
}
handleUnknownVanilla(p, ctxt, bean, propName);
} while ((propName = p.nextFieldName()) != null);
return bean;
}
return null;
}
}
아래와 같이 등록 할 수 있습니다.
class EmptyObjectIsNullBeanDeserializerModifier extends BeanDeserializerModifier {
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
if (beanDesc.getBeanClass() == Car.class) { //TODO: change this condition
return new EmptyObjectIsNullBeanDeserializer((BeanDeserializerBase) deserializer);
}
return super.modifyDeserializer(config, beanDesc, deserializer);
}
}
단순함 POC
:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonTokenId;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.BeanDeserializer;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBase;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.module.SimpleModule;
import lombok.Data;
import java.io.File;
import java.io.IOException;
public class EmptyObjectIsNullApp {
public static void main(String[] args) throws IOException {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
SimpleModule emptyObjectIsNullModule = new SimpleModule();
emptyObjectIsNullModule.setDeserializerModifier(new EmptyObjectIsNullBeanDeserializerModifier());
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(emptyObjectIsNullModule);
Wrapper wrapper = mapper.readValue(jsonFile, Wrapper.class);
System.out.println(wrapper);
}
}
@Data
class Wrapper {
private Car car;
}
@Data
class Car {
private int id;
}
"빈 객체" JSON
페이로드의 경우 :
{
"car": {}
}
위의 코드는 다음을 인쇄합니다.
Wrapper(car=null)
JSON
일부 필드가있는 페이로드의 경우 :
{
"car": {
"id": 1
}
}
위의 코드는 다음을 인쇄합니다.
Wrapper(car=Car(id=1))
이 기사는 인터넷에서 수집됩니다. 재 인쇄 할 때 출처를 알려주십시오.
침해가 발생한 경우 연락 주시기 바랍니다[email protected] 삭제
몇 마디 만하겠습니다