Làm cách nào để gọi bộ khử không khí mặc định từ bộ khử không khí tùy chỉnh ở Jackson


105

Tôi gặp sự cố trong bộ khử không khí tùy chỉnh của mình ở Jackson. Tôi muốn truy cập trình tuần tự hóa mặc định để điền vào đối tượng mà tôi đang giải mã. Sau khi dân số, tôi sẽ thực hiện một số điều tùy chỉnh nhưng trước tiên tôi muốn deserialize đối tượng với hành vi Jackson mặc định.

Đây là mã mà tôi có vào lúc này.

public class UserEventDeserializer extends StdDeserializer<User> {

  private static final long serialVersionUID = 7923585097068641765L;

  public UserEventDeserializer() {
    super(User.class);
  }

  @Override
  @Transactional
  public User deserialize(JsonParser jp, DeserializationContext ctxt)
      throws IOException, JsonProcessingException {

    ObjectCodec oc = jp.getCodec();
    JsonNode node = oc.readTree(jp);
    User deserializedUser = null;
    deserializedUser = super.deserialize(jp, ctxt, new User()); 
    // The previous line generates an exception java.lang.UnsupportedOperationException
    // Because there is no implementation of the deserializer.
    // I want a way to access the default spring deserializer for my User class.
    // How can I do that?

    //Special logic

    return deserializedUser;
  }

}

Những gì tôi cần là một cách để khởi tạo bộ giải mã mặc định để tôi có thể điền trước POJO của mình trước khi bắt đầu logic đặc biệt của mình.

Khi gọi deserialize từ bên trong bộ giải mã tùy chỉnh Có vẻ như phương thức được gọi từ ngữ cảnh hiện tại bất kể tôi xây dựng lớp tuần tự hóa như thế nào. Vì chú thích trong POJO của tôi. Điều này gây ra ngoại lệ Stack Overflow vì những lý do rõ ràng.

Tôi đã thử khởi tạo a BeanDeserializernhưng quá trình này cực kỳ phức tạp và tôi chưa tìm ra cách phù hợp để thực hiện. Tôi cũng đã thử quá tải nhưng AnnotationIntrospectorkhông có kết quả, nghĩ rằng nó có thể giúp tôi bỏ qua chú thích trong DeserializerContext. Cuối cùng, nó nối với nhau mà tôi có thể đã thành công khi sử dụng JsonDeserializerBuildersmặc dù điều này đòi hỏi tôi phải làm một số công cụ kỳ diệu để nắm được bối cảnh ứng dụng từ Spring. Tôi sẽ đánh giá cao bất kỳ điều nào có thể dẫn tôi đến một giải pháp rõ ràng hơn, chẳng hạn như cách Tôi có thể xây dựng bối cảnh deserialization mà không cần đọc JsonDeserializerchú thích.


2
Không. Những cách tiếp cận đó sẽ không hữu ích: vấn đề là bạn sẽ cần một bộ khử không khí mặc định được xây dựng hoàn chỉnh; và điều này yêu cầu một cái được xây dựng và sau đó bộ giải mã của bạn có quyền truy cập vào nó. DeserializationContextkhông phải là thứ bạn nên tạo hoặc thay đổi; nó sẽ được cung cấp bởi ObjectMapper. AnnotationIntrospector, tương tự như vậy, sẽ không giúp được gì trong việc truy cập.
StaxMan

Rốt cuộc bạn làm như thế nào?
khituras

Câu hỏi hay. Tôi không chắc nhưng tôi chắc chắn câu trả lời dưới đây đã giúp tôi. Tôi hiện không sở hữu mã mà chúng tôi đã viết nếu bạn tìm thấy giải pháp, vui lòng đăng nó ở đây cho người khác.
Pablo Jomer

Câu trả lời:


92

Như StaxMan đã gợi ý, bạn có thể làm điều này bằng cách viết a BeanDeserializerModifiervà đăng ký qua SimpleModule. Ví dụ sau sẽ hoạt động:

public class UserEventDeserializer extends StdDeserializer<User> implements ResolvableDeserializer
{
  private static final long serialVersionUID = 7923585097068641765L;

  private final JsonDeserializer<?> defaultDeserializer;

  public UserEventDeserializer(JsonDeserializer<?> defaultDeserializer)
  {
    super(User.class);
    this.defaultDeserializer = defaultDeserializer;
  }

  @Override public User deserialize(JsonParser jp, DeserializationContext ctxt)
      throws IOException, JsonProcessingException
  {
    User deserializedUser = (User) defaultDeserializer.deserialize(jp, ctxt);

    // Special logic

    return deserializedUser;
  }

  // for some reason you have to implement ResolvableDeserializer when modifying BeanDeserializer
  // otherwise deserializing throws JsonMappingException??
  @Override public void resolve(DeserializationContext ctxt) throws JsonMappingException
  {
    ((ResolvableDeserializer) defaultDeserializer).resolve(ctxt);
  }


  public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException
  {
    SimpleModule module = new SimpleModule();
    module.setDeserializerModifier(new BeanDeserializerModifier()
    {
      @Override public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer)
      {
        if (beanDesc.getBeanClass() == User.class)
          return new UserEventDeserializer(deserializer);
        return deserializer;
      }
    });


    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(module);
    User user = mapper.readValue(new File("test.json"), User.class);
  }
}

Cảm ơn! Tôi đã giải quyết vấn đề này theo cách khác nhưng tôi sẽ xem xét giải pháp của bạn khi tôi có thêm thời gian.
Pablo Jomer,

5
Có cách nào để làm tương tự nhưng với a JsonSerializer? Tôi có một số bộ tuần tự hóa nhưng chúng có mã chung nên tôi muốn tổng quát hóa nó. Tôi cố gắng gọi trực tiếp Bộ nối tiếp nhưng Kết quả không được mở trong kết quả JSON (mỗi lệnh gọi của bộ nối tiếp tạo một đối tượng mới)
herau

1
@herau BeanSerializerModifier, ResolvableSerializerContextualSerializerlà giao diện phù hợp để sử dụng cho serialization.
StaxMan

Điều này có áp dụng cho các hộp đựng phiên bản EE (Wildfly 10) không? Tôi nhận được JsonMappingException: (là java.lang.NullPointerException) (thông qua chuỗi tham chiếu: java.util.ArrayList [0])
user1927033

Câu hỏi sử dụng readTree()nhưng câu trả lời thì không. Ưu điểm của phương pháp này so với phương pháp được đăng bởi Derek Cochran là gì? Có cách nào để làm cho điều này hoạt động với readTree()?
Gili

14

Tôi đã tìm thấy câu trả lời tại ans dễ đọc hơn nhiều so với câu trả lời được chấp nhận.

    public User deserialize(JsonParser jp, DeserializationContext ctxt)
        throws IOException, JsonProcessingException {
            User user = jp.readValueAs(User.class);
             // some code
             return user;
          }

Nó thực sự không trở nên dễ dàng hơn thế này.


Chào Gili! Cảm ơn vì nó, tôi hy vọng rằng mọi người tìm thấy câu trả lời này và có thời gian để xác nhận nó. Tôi không còn có thể làm được vì vậy tôi không thể chấp nhận câu trả lời vào lúc này. Nếu tôi thấy mọi người nói rằng đây là một giải pháp khả thi, tất nhiên tôi sẽ hướng dẫn họ theo hướng đó. Nó cũng có thể là điều này là không thể cho tất cả các phiên bản. Vẫn cảm ơn vì đã chia sẻ.
Pablo Jomer

Không biên dịch với Jackson 2.9.9. JsonParser.readTree () không tồn tại.
ccleve

@ccleve Có vẻ như một lỗi đánh máy đơn giản. Đã sửa.
Gili

Có thể xác nhận rằng điều này hoạt động với Jackson 2.10, cảm ơn!
Stuart Leyland-Cole

1
Tôi không hiểu điều này hoạt động như thế nào, điều này dẫn đến một StackOverflowError, vì Jackson sẽ sử dụng lại cùng một bộ tuần tự cho User...
john16384

12

DeserializationContextmột readValue()phương pháp mà bạn có thể sử dụng. Điều này sẽ hoạt động đối với cả bộ giải không khí mặc định và bất kỳ bộ giải mã nào tùy chỉnh mà bạn có.

Chỉ cần chắc chắn để gọi traverse()trên JsonNodemức bạn muốn đọc để lấy JsonParserđể vượt qua readValue().

public class FooDeserializer extends StdDeserializer<FooBean> {

    private static final long serialVersionUID = 1L;

    public FooDeserializer() {
        this(null);
    }

    public FooDeserializer(Class<FooBean> t) {
        super(t);
    }

    @Override
    public FooBean deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        JsonNode node = jp.getCodec().readTree(jp);
        FooBean foo = new FooBean();
        foo.setBar(ctxt.readValue(node.get("bar").traverse(), BarBean.class));
        return foo;
    }

}

DeserialisationContext.readValue () không tồn tại, đó là một phương pháp ObjectMapper
Pedro Borges

giải pháp này đang hoạt động tốt, tuy nhiên bạn có thể cần phải gọi nextToken () nếu bạn deserialize một lớp giá trị ví dụ Date.class
revau.lt

9

Có một số cách để làm điều này, nhưng để làm đúng thì đòi hỏi nhiều công việc hơn. Về cơ bản, bạn không thể sử dụng phân lớp con, vì thông tin mặc định bộ giải mã cần được xây dựng từ các định nghĩa lớp.

Vì vậy, những gì bạn có thể sử dụng nhất là xây dựng một BeanDeserializerModifier, đăng ký thông qua Modulegiao diện (sử dụng SimpleModule). Bạn cần xác định / ghi đè modifyDeserializervà đối với trường hợp cụ thể mà bạn muốn thêm logic của riêng mình (trong đó loại khớp với), hãy xây dựng bộ giải mã trên không của riêng bạn, chuyển bộ giải mã mặc định mà bạn được cung cấp. Và sau đó trong deserialize()phương thức, bạn chỉ có thể ủy quyền cuộc gọi, lấy đối tượng kết quả.

Ngoài ra, nếu bạn thực sự phải tạo và điền đối tượng, bạn có thể làm như vậy và gọi phiên bản quá tải của deserialize()nó có đối số thứ ba; đối tượng để deserialize vào.

Một cách khác có thể hoạt động (nhưng không chắc chắn 100%) là chỉ định Converterđối tượng ( @JsonDeserialize(converter=MyConverter.class)). Đây là một tính năng mới của Jackson 2.2. Trong trường hợp của bạn, Bộ chuyển đổi sẽ không thực sự chuyển đổi loại, nhưng đơn giản hóa việc sửa đổi đối tượng: nhưng tôi không biết liệu điều đó có cho phép bạn làm chính xác những gì bạn muốn hay không, vì trình giải mã mặc định sẽ được gọi trước tiên và sau đó là của bạn Converter.


Câu trả lời của tôi vẫn là: bạn cần để Jackson xây dựng bộ khử không khí mặc định để ủy quyền; và phải tìm cách "ghi đè" nó. BeanDeserializerModifierlà trình xử lý gọi lại cho phép điều đó.
StaxMan

7

Nếu bạn có thể khai báo lớp Người dùng bổ sung thì bạn có thể triển khai nó chỉ bằng cách sử dụng chú thích

// your class
@JsonDeserialize(using = UserEventDeserializer.class)
public class User {
...
}

// extra user class
// reset deserializer attribute to default
@JsonDeserialize
public class UserPOJO extends User {
}

public class UserEventDeserializer extends StdDeserializer<User> {

  ...
  @Override
  public User deserialize(JsonParser jp, DeserializationContext ctxt)
      throws IOException, JsonProcessingException {
    // specify UserPOJO.class to invoke default deserializer
    User deserializedUser = jp.ReadValueAs(UserPOJO.class);
    return deserializedUser;

    // or if you need to walk the JSON tree

    ObjectMapper mapper = (ObjectMapper) jp.getCodec();
    JsonNode node = oc.readTree(jp);
    // specify UserPOJO.class to invoke default deserializer
    User deserializedUser = mapper.treeToValue(node, UserPOJO.class);

    return deserializedUser;
  }

}

1
Đúng vậy. Cách tiếp cận duy nhất phù hợp với tôi. Tôi đã nhận được StackOverflowErrors vì một lệnh gọi đệ quy tới bộ khử không khí.
ccleve

2

Đây là oneliner sử dụng ObjectMapper

public MyObject deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
    OMyObject object = new ObjectMapper().readValue(p, MyObject.class);
    // do whatever you want 
    return object;
}

Và xin vui lòng: Thực sự không cần phải sử dụng bất kỳ giá trị Chuỗi nào hoặc thứ gì khác. Tất cả thông tin cần thiết đều được cung cấp bởi JsonParser, vì vậy hãy sử dụng nó.


2

Cùng với những gì Tomáš Záluský đã đề xuất , trong những trường hợp BeanDeserializerModifierkhông mong muốn việc sử dụng, bạn có thể tự mình tạo một bộ khử không khí mặc định bằng cách sử dụng BeanDeserializerFactory, mặc dù cần thiết phải có thêm một số thiết lập. Theo ngữ cảnh, giải pháp này sẽ giống như sau:

public User deserialize(JsonParser jp, DeserializationContext ctxt)
  throws IOException, JsonProcessingException {

    ObjectCodec oc = jp.getCodec();
    JsonNode node = oc.readTree(jp);
    User deserializedUser = null;

    DeserializationConfig config = ctxt.getConfig();
    JavaType type = TypeFactory.defaultInstance().constructType(User.class);
    JsonDeserializer<Object> defaultDeserializer = BeanDeserializerFactory.instance.buildBeanDeserializer(ctxt, type, config.introspect(type));

    if (defaultDeserializer instanceof ResolvableDeserializer) {
        ((ResolvableDeserializer) defaultDeserializer).resolve(ctxt);
    }

    JsonParser treeParser = oc.treeAsTokens(node);
    config.initialize(treeParser);

    if (treeParser.getCurrentToken() == null) {
        treeParser.nextToken();
    }

    deserializedUser = (User) defaultDeserializer.deserialize(treeParser, context);

    return deserializedUser;
}

1

Tôi không hài lòng với việc sử dụng BeanSerializerModifiervì nó buộc phải khai báo một số thay đổi hành vi trong trung tâm ObjectMapperchứ không phải trong chính bộ giải mã tùy chỉnh và trên thực tế, nó là giải pháp song song để chú thích lớp thực thể với JsonSerialize. Nếu bạn cảm thấy nó theo cách tương tự, bạn có thể đánh giá cao câu trả lời của tôi tại đây: https://stackoverflow.com/a/43213463/653539


1

Một giải pháp đơn giản hơn cho tôi là chỉ cần thêm một bean khác ObjectMappervà sử dụng nó để giải không khí cho đối tượng (nhờ vào https://stackoverflow.com/users/1032167/varren comment) - trong trường hợp của tôi, tôi quan tâm đến việc giải không khí cho id của nó (một int) hoặc toàn bộ đối tượng https://stackoverflow.com/a/46618193/986160

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();
          // do your custom deserialization here using json
          // and decide when to use default deserialization using local objectMapper:
          T obj = objectMapper().readValue(json, clazz);

          return obj;
     }
}

đối với mỗi thực thể cần thông qua bộ giải mã tùy chỉnh, chúng ta cần định cấu hình nó trong ObjectMapperbean toàn cầu của Ứng dụng khởi động mùa xuân trong trường hợp của tôi (ví dụ: đối với Category):

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

0

Bạn chắc chắn sẽ thất bại nếu bạn cố gắng tạo bộ khử không khí tùy chỉnh của mình từ đầu.

Thay vào đó, bạn cần nắm giữ phiên bản deserializer mặc định (được cấu hình đầy đủ) thông qua một tùy chỉnh BeanDeserializerModifier, sau đó chuyển phiên bản này vào lớp deserializer tùy chỉnh của bạn:

public ObjectMapper getMapperWithCustomDeserializer() {
    ObjectMapper objectMapper = new ObjectMapper();

    SimpleModule module = new SimpleModule();
    module.setDeserializerModifier(new BeanDeserializerModifier() {
        @Override
        public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
                    BeanDescription beanDesc, JsonDeserializer<?> defaultDeserializer) 
            if (beanDesc.getBeanClass() == User.class) {
                return new UserEventDeserializer(defaultDeserializer);
            } else {
                return defaultDeserializer;
            }
        }
    });
    objectMapper.registerModule(module);

    return objectMapper;
}

Lưu ý: Đăng ký mô-đun này thay thế @JsonDeserializechú thích, tức là Userlớp hoặcUser các trường sẽ không còn được chú thích bằng chú thích này nữa.

Sau đó, bộ giải mã tùy chỉnh phải dựa trên a DelegatingDeserializerđể tất cả các phương thức ủy quyền, trừ khi bạn cung cấp triển khai rõ ràng:

public class UserEventDeserializer extends DelegatingDeserializer {

    public UserEventDeserializer(JsonDeserializer<?> delegate) {
        super(delegate);
    }

    @Override
    protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegate) {
        return new UserEventDeserializer(newDelegate);
    }

    @Override
    public User deserialize(JsonParser p, DeserializationContext ctxt)
            throws IOException {
        User result = (User) super.deserialize(p, ctxt);

        // add special logic here

        return result;
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.