杰克逊 vs. Spring HATEOAS vs. 多态性

5
当我尝试反序列化一个具有多态成员的实体时,Jackson会抛出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\"])"

所有实际工作都由Spring HATEOAS的PagingAndSortingRepository完成。

我使用的是spring-boot V 1.2.4.RELEASE版本,这意味着jackson是V 2.4.6版本,Spring HATEOAS是V 0.16.0.RELEASE版本。

示例:

我家里有一只宠物:

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

}

那个宠物是一种动物 - 在这种情况下可能是猫或狗。它的类型由@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 {

}

还有这个方便的PagingAndSortingRepository,它允许我通过REST/HATEOAS访问我的主页...

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

}

为了确认所有东西都正常工作,我已经设置了一个测试...
@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")));

    }

}

有趣的是,我可以将猫作为宠物创建我的家,但当我想要更新猫的名字时,它无法反序列化JSON了... 有什么建议吗?

当你想要更改动物的名称时,为什么要更新“home”?你的“put”会创建一个新的动物。 - a better oliver
1个回答

4
我将尝试给出一个半答案。
在处理PUT(可能也适用于PATCH)时,spring-data-rest-webmvc将给定的JSON数据合并到现有实体中。在这样做时,它会从JSON树中删除所有不存在于实体中的属性,然后再将其传递给Jackson ObjectMapper进行反序列化操作。换句话说,当Jackson开始反序列化对象时,您的@class属性已经消失了。
你可以通过将@class属性作为实体的实际属性添加来解决这个问题(仅用于测试/演示目的)。现在一切都将正常工作,但是您的实体现在拥有一个否则无用的classname属性,这可能不是您想要的。
使用@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=As.WRAPPER_OBJECT)方法也行不通,原因类似(除了这次整个包装器对象被删除)。与最初的方法一样,GET和POST将正常工作。
整个问题看起来像一个bug或@JsonTypeInfo在spring-data-rest-webmvc中不受支持的情况。
也许其他人可以对此提供更多的解释。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接