ฉันจะเรียก deserializer เริ่มต้นจาก deserializer แบบกำหนดเองใน Jackson ได้อย่างไร


107

ฉันมีปัญหาใน deserializer ที่กำหนดเองใน Jackson ฉันต้องการเข้าถึง Serializer เริ่มต้นเพื่อเติมข้อมูลวัตถุที่ฉันกำลังแยกส่วน หลังจากมีประชากรฉันจะทำสิ่งที่กำหนดเอง แต่ก่อนอื่นฉันต้องการยกเลิกการกำหนดค่าเริ่มต้นของวัตถุด้วยพฤติกรรมเริ่มต้นของแจ็คสัน

นี่คือรหัสที่ฉันมีอยู่ในขณะนี้

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

}

สิ่งที่ฉันต้องการคือวิธีเริ่มต้น deserializer เริ่มต้นเพื่อให้ฉันสามารถเติม POJO ของฉันล่วงหน้าก่อนที่จะเริ่มตรรกะพิเศษของฉัน

เมื่อเรียก deserialize จากภายใน deserializer ที่กำหนดเองดูเหมือนว่าเมธอดจะถูกเรียกจากบริบทปัจจุบันไม่ว่าฉันจะสร้างคลาส serializer อย่างไร เนื่องจากคำอธิบายประกอบใน POJO ของฉัน สิ่งนี้ทำให้เกิดข้อยกเว้น Stack Overflow ด้วยเหตุผลที่ชัดเจน

ฉันได้ลองเริ่มต้นแล้วBeanDeserializerแต่กระบวนการนี้ซับซ้อนมากและฉันไม่สามารถหาวิธีที่ถูกต้องได้ ฉันได้พยายามมากเกินไปจนAnnotationIntrospectorไม่มีประโยชน์โดยคิดว่าอาจช่วยให้ฉันเพิกเฉยต่อคำอธิบายประกอบในไฟล์DeserializerContext. ในที่สุดฉันก็อาจประสบความสำเร็จในการใช้JsonDeserializerBuildersแม้ว่าสิ่งนี้จะทำให้ฉันต้องทำของวิเศษเพื่อให้ได้บริบทของแอปพลิเคชันจาก Spring ฉันจะขอบคุณทุกสิ่งที่สามารถนำฉันไปสู่โซลูชันที่สะอาดยิ่งขึ้นเช่นฉันจะสร้างบริบท deserialization ได้อย่างไรโดยไม่ต้องอ่านJsonDeserializerคำอธิบายประกอบ


2
ไม่วิธีการเหล่านี้จะไม่ช่วย: ปัญหาคือคุณจะต้องใช้ deserializer เริ่มต้นที่สร้างขึ้นอย่างสมบูรณ์ และสิ่งนี้ต้องการให้มีการสร้างขึ้นจากนั้น deserializer ของคุณจะสามารถเข้าถึงได้ DeserializationContextไม่ใช่สิ่งที่คุณควรสร้างหรือเปลี่ยนแปลง ObjectMapperก็จะมีการให้บริการโดย AnnotationIntrospectorในทำนองเดียวกันจะไม่ช่วยในการเข้าถึง
StaxMan

สุดท้ายแล้วคุณจะทำมันได้อย่างไร?
khituras

คำถามที่ดี. ฉันไม่แน่ใจ แต่ฉันแน่ใจว่าคำตอบด้านล่างช่วยฉันได้ ขณะนี้ฉันไม่ได้ครอบครองรหัสที่เราเขียนไว้หากคุณพบวิธีแก้ไขโปรดโพสต์ไว้ที่นี่เพื่อให้ผู้อื่นทราบ
Pablo Jomer

คำตอบ:


93

ตามที่ StaxMan แนะนำแล้วคุณสามารถทำได้โดยเขียน a BeanDeserializerModifierและลงทะเบียนผ่านSimpleModule. ตัวอย่างต่อไปนี้ควรใช้งานได้:

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

ขอบคุณ! ฉันแก้ปัญหานี้ด้วยวิธีอื่นแล้ว แต่ฉันจะตรวจสอบวิธีแก้ปัญหาของคุณเมื่อฉันมีเวลามากขึ้น
Pablo Jomer

5
มีวิธีทำเหมือนกัน แต่มีJsonSerializerไหม? ฉันมีซีเรียลไลเซอร์หลายตัว แต่มีโค้ดเหมือนกันดังนั้นฉันจึงต้องการระบุมัน ฉันพยายามโทรหา The serializer โดยตรง แต่ผลลัพธ์ไม่ได้ถูกคลายออกในผลลัพธ์ JSON (การเรียกใช้ serializer แต่ละครั้งจะสร้างวัตถุใหม่)
herau

1
@herau BeanSerializerModifier, ResolvableSerializerและContextualSerializerมีอินเตอร์เฟซที่ตรงกันที่จะใช้สำหรับเป็นอันดับ
StaxMan

สิ่งนี้ใช้ได้กับคอนเทนเนอร์รุ่น EE (Wildfly 10) หรือไม่ ฉันได้รับ JsonMappingException: (คือ java.lang.NullPointerException) (ผ่านห่วงโซ่อ้างอิง: java.util.ArrayList [0])
user1927033

คำถามใช้readTree()แต่คำตอบไม่ได้ อะไรคือข้อดีของแนวทางนี้เมื่อเทียบกับแนวทางที่Derek Cochran โพสต์ไว้ ? มีวิธีทำให้งานนี้ด้วยreadTree()หรือไม่?
Gili

15

ฉันพบคำตอบที่ansซึ่งอ่านได้ง่ายกว่าคำตอบที่ยอมรับ

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

มันไม่ง่ายไปกว่านี้แล้ว


สวัสดี Gili! ขอบคุณที่หวังว่าผู้คนจะพบคำตอบนี้และมีเวลาตรวจสอบ ฉันไม่อยู่ในสถานะที่จะทำเช่นนั้นอีกต่อไปเพราะฉันไม่สามารถยอมรับคำตอบได้ในขณะนี้ หากฉันเห็นว่ามีคนพูดว่านี่เป็นวิธีแก้ปัญหาที่เป็นไปได้ฉันจะแนะนำพวกเขาให้ทำ นอกจากนี้ยังอาจเป็นไปไม่ได้สำหรับทุกเวอร์ชัน ยังคงขอบคุณสำหรับการแบ่งปัน
Pablo Jomer

ไม่ได้รวบรวมกับ Jackson 2.9.9 ไม่มี JsonParser.readTree ()
ccleve

@ccleve ดูเหมือนจะพิมพ์ผิดง่ายๆ แก้ไขแล้ว.
Gili

สามารถยืนยันได้ว่าสิ่งนี้ใช้ได้กับ Jackson 2.10 ขอบคุณ!
Stuart Leyland-Cole

3
ฉันไม่เข้าใจว่ามันทำงานอย่างไรซึ่งส่งผลให้เกิด a StackOverflowErrorเนื่องจากแจ็คสันจะใช้ซีเรียลUser
ไลเซอร์

13

DeserializationContextมีreadValue()วิธีการที่คุณอาจจะใช้ สิ่งนี้ควรใช้ได้กับทั้ง deserializer เริ่มต้นและ deserializers แบบกำหนดเองที่คุณมี

เพียงให้แน่ใจว่าจะเรียกtraverse()ในJsonNodeระดับที่คุณต้องการที่จะอ่านเพื่อดึงเพื่อส่งผ่านไปJsonParserreadValue()

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 () ไม่มีอยู่นั่นคือวิธีการของ ObjectMapper
Pedro Borges

โซลูชันนี้ทำงานได้ดีอย่างไรก็ตามคุณอาจต้องเรียก nextToken () หากคุณยกเลิกการกำหนดค่าคลาสของค่าเช่น Date.class
revau.lt

9

มีสองวิธีในการทำเช่นนี้ แต่การทำให้ถูกต้องต้องทำงานมากขึ้นเล็กน้อย โดยทั่วไปคุณไม่สามารถใช้การแบ่งประเภทย่อยได้เนื่องจากความต้องการ deserializers เริ่มต้นของข้อมูลถูกสร้างขึ้นจากนิยามคลาส

ดังนั้นสิ่งที่คุณสามารถใช้ได้มากที่สุดคือสร้าง a BeanDeserializerModifierลงทะเบียนผ่านModuleอินเทอร์เฟซ (ใช้SimpleModule) คุณต้องกำหนด / แทนที่modifyDeserializerและสำหรับกรณีเฉพาะที่คุณต้องการเพิ่มลอจิกของคุณเอง (ตรงกับประเภท) ให้สร้าง deserializer ของคุณเองส่งผ่าน deserializer เริ่มต้นที่คุณกำหนด แล้วในdeserialize()วิธีการที่คุณสามารถมอบหมายการโทรรับวัตถุผลลัพธ์

หรือถ้าคุณต้องสร้างและเติมข้อมูลออบเจ็กต์จริงๆคุณสามารถทำได้และเรียกใช้เวอร์ชันที่โอเวอร์โหลดdeserialize()ซึ่งรับอาร์กิวเมนต์ที่สาม วัตถุที่จะ deserialize เป็น

อีกวิธีหนึ่งที่อาจใช้ได้ผล (แต่ไม่แน่ใจ 100%) คือการระบุConverterobject ( @JsonDeserialize(converter=MyConverter.class)) นี่คือคุณสมบัติใหม่ของ Jackson 2.2 ในกรณีของคุณ Converter จะไม่แปลงประเภทจริง ๆ แต่ทำให้การปรับเปลี่ยนวัตถุง่ายขึ้น: แต่ฉันไม่รู้ว่าจะช่วยให้คุณทำสิ่งที่คุณต้องการได้หรือไม่เนื่องจาก deserializer เริ่มต้นจะถูกเรียกก่อนจากนั้นจึงเรียกConverterไฟล์.


คำตอบของฉันยังคงยืนอยู่: คุณต้องให้ Jackson สร้าง deserializer เริ่มต้นเพื่อมอบหมายให้ และต้องหาวิธี "ลบล้าง" มัน BeanDeserializerModifierคือตัวจัดการการโทรกลับที่อนุญาต
StaxMan

7

หากเป็นไปได้ที่คุณจะประกาศคลาส User พิเศษคุณสามารถใช้งานได้โดยใช้คำอธิบายประกอบ

// 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
ได้. แนวทางเดียวที่ใช้ได้ผลสำหรับฉัน ฉันได้รับ StackOverflowErrors เนื่องจากมีการเรียกซ้ำไปยัง deserializer
ccleve

3

ตามบรรทัดของสิ่งที่TomášZáluskýแนะนำในกรณีที่การใช้งานBeanDeserializerModifierไม่เป็นที่ต้องการคุณสามารถสร้าง deserializer เริ่มต้นด้วยตัวคุณเองได้BeanDeserializerFactoryแม้ว่าจะมีการตั้งค่าเพิ่มเติมที่จำเป็น ในบริบทโซลูชันนี้จะมีลักษณะดังนี้:

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

สิ่งนี้ได้ผลเหมือนฝันกับ Jackson 2.9.9 ไม่ได้รับผลกระทบจาก StackOverflowError เหมือนตัวอย่างอื่น ๆ ที่ให้ไว้
meta1203

2

นี่คือ oneliner โดยใช้ 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;
}

และโปรด: ไม่จำเป็นต้องใช้ค่า String หรืออย่างอื่น ข้อมูลที่จำเป็นทั้งหมดได้รับจาก JsonParser ดังนั้นจงใช้มัน


1

ฉันไม่ได้ตกลงกับการใช้BeanSerializerModifierตั้งแต่มันบังคับที่จะประกาศการเปลี่ยนแปลงพฤติกรรมบางอย่างในกลางObjectMapperมากกว่าในการกำหนด deserializer ตัวเองและในความเป็นจริงมันเป็นวิธีการแก้ขนานกับระดับนิติบุคคล annotating JsonSerializeกับ หากคุณรู้สึกเหมือนกันคุณอาจพอใจกับคำตอบของฉันที่นี่: https://stackoverflow.com/a/43213463/653539


1

วิธีแก้ปัญหาที่ง่ายกว่าสำหรับฉันคือเพิ่มถั่วอื่นObjectMapperและใช้มันเพื่อแยกส่วนของวัตถุ (ขอบคุณhttps://stackoverflow.com/users/1032167/varrenความคิดเห็น) - ในกรณีของฉันฉันสนใจที่จะ deserialize ไปยัง id ของมัน (int) หรือวัตถุทั้งหมด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;
     }
}

สำหรับแต่ละเอนทิตีที่ต้องดำเนินการผ่าน deserializer แบบกำหนดเองเราจำเป็นต้องกำหนดค่าในObjectMapperbean ส่วนกลางของ Spring Boot App ในกรณีของฉัน (เช่นสำหรับ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

คุณจะล้มเหลวหากคุณพยายามสร้าง deserializer แบบกำหนดเองตั้งแต่เริ่มต้น

แต่คุณต้องรับอินสแตนซ์ deserializer เริ่มต้น (กำหนดค่าเต็ม) ผ่านแบบกำหนดเองBeanDeserializerModifierจากนั้นส่งอินสแตนซ์นี้ไปยังคลาส deserializer ที่กำหนดเอง

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

หมายเหตุ: การลงทะเบียนโมดูลนี้จะแทนที่@JsonDeserializeคำอธิบายประกอบกล่าวคือไม่ควรใส่คำอธิบายประกอบUserคลาสหรือUserฟิลด์ด้วยคำอธิบายประกอบนี้อีกต่อไป

จากนั้น deserializer ที่กำหนดเองควรจะขึ้นอยู่กับDelegatingDeserializerวิธีการทั้งหมดที่มอบหมายเว้นแต่คุณจะให้การใช้งานที่ชัดเจน:

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

0

การใช้BeanDeserializerModifierงานได้ผลดี แต่ถ้าคุณจำเป็นต้องใช้JsonDeserializeมีวิธีการAnnotationIntrospector ดังนี้:

ObjectMapper originalMapper = new ObjectMapper();
ObjectMapper copy = originalMapper.copy();//to keep original configuration
copy.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {

            @Override
            public Object findDeserializer(Annotated a) {
                Object deserializer = super.findDeserializer(a);
                if (deserializer == null) {
                    return null;
                }
                if (deserializer.equals(MyDeserializer.class)) {
                    return null;
                }
                return deserializer;
            }
});

ตอนนี้ผู้ทำแผนที่ที่คัดลอกจะไม่สนใจ deserializer ที่คุณกำหนดเอง (MyDeserializer.class) และใช้การใช้งานเริ่มต้น คุณสามารถใช้มันภายในdeserializeวิธีการของ deserializer ที่กำหนดเองเพื่อหลีกเลี่ยงการเรียกซ้ำโดยการทำสำเนา mapper แบบคงที่หรือต่อสายหากใช้ Spring

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.