简体   繁体   English

杰克逊与春天的HATEOAS对比多态性

[英]Jackson vs. Spring HATEOAS vs. Polymorphism

When I want to deserialize an Entity with a polymorph member, Jackson throws a com.fasterxml.jackson.databind.JsonMappingException , complaining about a missing type info (...which is actually present in the JSON -> see example). 当我想用一个多态成员反序列化一个实体时,杰克逊抛出一个com.fasterxml.jackson.databind.JsonMappingException ,抱怨缺少类型信息(...实际上存在于JSON中 - >参见示例)。

Unexpected token (END_OBJECT), expected FIELD_NAME: missing property '@class' that is to contain type id  (for class demo.animal.Animal)\n at [Source: N/A; line: -1, column: -1] (through reference chain: demo.home.Home[\"pet\"])"

All actual work is done by a PagingAndSortingRepository from Spring HATEOAS. 所有实际工作都由Spring HATEOAS的PagingAndSortingRepository完成。

I use spring-boot V 1.2.4.RELEASE, which means jackson is V 2.4.6 and Spring HATEOAS is V 0.16.0.RELEASE. 我使用spring-boot V 1.2.4.RELEASE,这意味着jackson是V 2.4.6而Spring HATEOAS是V 0.16.0.RELEASE。

Example: 例:

I have a pet at home: 我家里有一只宠物:

@Entity
public class Home {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    @OneToOne(cascade = {CascadeType.ALL})
    private Animal pet;

    public Animal getPet() {
        return pet;
    }

    public void setPet(Animal pet) {
        this.pet = pet;
    }

}

That Pet is some Animal - in this case either a Cat or a Dog. 宠物是动物 - 在这种情况下是猫或狗。 It's type is identified by the @class property... 它的类型由@class属性标识...

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
public abstract class Animal {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

@Entity
public class Cat extends Animal {

}

@Entity
public class Dog extends Animal {

}

Then there is this handy PagingAndSortingRepository, which allows me to access my home via REST/HATEOAS... 然后有这个方便的PagingAndSortingRepository,它允许我通过REST / HATEOAS访问我的家...

@RepositoryRestResource(collectionResourceRel = "home", path = "home")
public interface HomeRepository extends PagingAndSortingRepository<Home, Integer> {

}

To confirm all that stuff is working, I have a test in place... 为了确认所有这些东西都在工作,我有一个测试...

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = DemoApplication.class)
@WebAppConfiguration
public class HomeIntegrationTest {

    @Autowired
    private WebApplicationContext ctx;

    private MockMvc mockMvc;

    @Before
    public void setUp() {
        this.mockMvc = webAppContextSetup(ctx).build();
    }

    @Test
    public void testRename() throws Exception {

        // I create my home with some cat...
        // http://de.wikipedia.org/wiki/Schweizerdeutsch#Wortschatz -> Büsi
        MockHttpServletRequestBuilder post = post("/home/")
                .content("{\"pet\": {\"@class\": \"demo.animal.Cat\", \"name\": \"Büsi\"}}");
        mockMvc.perform(post).andDo(print()).andExpect(status().isCreated());

        // Confirm that the POST request works nicely, so the JSON thingy is correct...
        MockHttpServletRequestBuilder get1 = get("/home/").accept(MediaType.APPLICATION_JSON);
        mockMvc.perform(get1).andDo(print()).andExpect(status().isOk())
                .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$._embedded.home", hasSize(1)))
                .andExpect(jsonPath("$._embedded.home[0].pet.name", is("Büsi")));

        // Now the interesting part: let's give that poor kitty a proper name...
        MockHttpServletRequestBuilder put = put("/home/1")
                .content("{\"pet\": {\"@class\": \"demo.animal.Cat\", \"name\": \"Beauford\"}}");
        mockMvc.perform(put).andDo(print()).andExpect(status().isNoContent());
        // PUT will thow JsonMappingException exception about an missing "@class"...

        MockHttpServletRequestBuilder get2 = get("/home/").accept(MediaType.APPLICATION_JSON);
        mockMvc.perform(get2).andDo(print()).andExpect(status().isOk())
                .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$._embedded.home", hasSize(1)))
                .andExpect(jsonPath("$._embedded.home[0].pet.name", is("Beaufort")));

    }

}

Interestingly I can create my home with the cat as a pet, but when I want to update the name of the cat it cannot deserialize the JSON anymore... 有趣的是,我可以用猫作为宠物创建我的家,但是当我想要更新猫的名字时,它不能再反序列化JSON了...

Any suggestions? 有什么建议?

I'm going to attempt a half-answer. 我打算尝试半答案。

When processing a PUT (probably PATCH as well), spring-data-rest-webmvc merges the given JSON data into the existing entity. 在处理PUT(也可能是PATCH)时, spring-data-rest-webmvc将给定的JSON数据合并到现有实体中。 While doing so, it strips all properties that don't exist in the entity from the JSON tree before passing it to the Jackson ObjectMapper . 在执行此操作时,它会在将实体传递给Jackson ObjectMapper 之前从JSON树中ObjectMapper实体中不存在的所有属性。 In other words, your @class property is gone by the time Jackson gets to deserialize your object. 换句话说,当杰克逊反序列化你的对象时,你的@class属性就消失了。

You can work around this (for testing/demonstration purposes) by adding your @class property as an actual property to your entity (you have to rename it of course, say classname ). 您可以通过将@class属性作为实际属性添加到您的实体来解决此问题(用于测试/演示目的)(当然,您必须重命名它,比如classname )。 Now everything will work fine, however your entity now has an otherwise useless classname property, which is probably not what you want. 现在一切都会正常工作,但是你的实体现在有一个无用的classname属性,这可能不是你想要的。

Using the @JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=As.WRAPPER_OBJECT) approach also won't work, for a similar reason (except this time the entire wrapper object is removed). 使用@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=As.WRAPPER_OBJECT)方法也不起作用,原因类似(除了这次删除整个包装器对象)。 Also as with the original approach, GET and POST will work fine. 与原始方法一样,GET和POST也能正常工作。

The whole thing looks like a bug or @JsonTypeInfo not supported in spring-data-rest-webmvc situation. 整个事情看起来像一个bug或@JsonTypeInfospring-data-rest-webmvc情况下spring-data-rest-webmvc

Maybe somebody else can shed some more light on this. 也许其他人可以对此有所了解。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM