jackson을 사용한 역 직렬화 후 빈 pojos를 유발하는 "car": {}와 같은 빈 json 객체를 무시하는 방법

요헨 부흐 홀츠

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를 사용합니다.

Michał Ziober

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] 삭제

에서 수정
0

몇 마디 만하겠습니다

0리뷰
로그인참여 후 검토

관련 기사

Related 관련 기사

뜨겁다태그

보관