2015-06-15 10 views
5

Wenn ich eine Entität mit einem polymorphen Mitglied deserialisieren will, wirft Jackson eine com.fasterxml.jackson.databind.JsonMappingException, beschwert sich über eine fehlende Typ Info (... die tatsächlich in der JSON vorhanden ist -> siehe Beispiel) .Jackson vs Spring HATEOAS vs Polymorphismus

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\"])" 

Alle tatsächliche Arbeit wird von einem PagingAndSortingRepository von Spring HATEOAS getan.

Ich benutze Spring-Boot V 1.2.4.RELEASE, was bedeutet, Jackson ist V 2.4.6 und Spring HATEOAS ist V 0.16.0.RELEASE.

Beispiel:

Ich habe ein Haustier zu Hause:

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

} 

Das Pet einige Tier ist - in diesem Fall entweder eine Katze oder ein Hund. Es Typ wird von der @class Eigenschaft identifiziert ...

@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 { 

} 

Dann gibt es dieses handliche PagingAndSortingRepository, die mir meine Heimat über REST/HATEOAS ...

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

} 

zugreifen können alle bestätigen das Zeug funktioniert, ich einen Test an der richtigen Stelle ...

@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"))); 

    } 

} 

Interessanter kann ich mein Haus mit der Katze als Haustier erstellen, aber wenn ich den Namen der Katze aktualisieren möchten sie nicht die J deserialisieren kann SON mehr ...

Irgendwelche Vorschläge?

+0

Warum aktualisieren Sie 'home', wenn Sie den Namen des Tieres ändern möchten? Dein 'Put' würde ein neues Tier erschaffen. – zeroflagL

Antwort

4

Ich werde eine halbe Antwort versuchen.

Bei der Verarbeitung eines PUT (wahrscheinlich auch PATCH) führt spring-data-rest-webmvc die angegebenen JSON-Daten in die vorhandene Entität ein. Dabei werden alle Eigenschaften, die in der Entität nicht vorhanden sind, aus dem JSON-Baum vor entfernt und an die Jackson ObjectMapper übergeben. Mit anderen Worten, Ihre @class Eigenschaft ist zu der Zeit verschwunden, als Jackson Ihr Objekt deserialisiert.

Sie können dies umgehen (zu Test-/Demonstrationszwecken), indem Sie Ihre @class Eigenschaft als eine tatsächliche Eigenschaft zu Ihrer Entität hinzufügen (Sie müssen es natürlich umbenennen, sagen classname). Jetzt wird alles gut, aber Ihre Entität hat jetzt eine ansonsten nutzlose classname Eigenschaft, die wahrscheinlich nicht das ist, was Sie wollen.

Die Verwendung des @JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=As.WRAPPER_OBJECT) Ansatz funktioniert auch nicht, aus einem ähnlichen Grund (außer dieses Mal das gesamte Wrapper-Objekt entfernt wird). Wie beim ursprünglichen Ansatz funktionieren auch GET und POST einwandfrei.

Die ganze Sache sieht wie ein Fehler oder @JsonTypeInfo nicht in spring-data-rest-webmvc Situation unterstützt.

Vielleicht kann jemand anderes mehr Licht auf dieses Thema werfen.

Verwandte Themen