toedter / spring-hateoas-jsonapi

A JSON:API media type implementation for Spring HATEOAS
Apache License 2.0
106 stars 15 forks source link

What is the proper way to handle PATCH? #51

Closed sadam21 closed 2 years ago

sadam21 commented 2 years ago

What is the recommended way to handle PATCH request for entities with several attributes and relationships? What should DTOs look like?

I saw example https://github.com/toedter/spring-hateoas-jsonapi/blob/master/lib/src/test/java/com/toedter/spring/hateoas/jsonapi/support/WebMvcMovieController.java .

We have to differentiate usecases:

Our solution: We've introduced for our use case OptionalProperty<T> interface which is "wrapper" around entity property. Its implementation NoneProperty represents property which isn't sent in PATCH and SomeProperty<T> (property was sent).

class Person {
  //attributes
  private String name;
  private Integer age;

  // relationships
  private City city;
  private List<Job> jobs;
}
class PatchPerson {
  //attributes
  private OptionalProperty<String> name;
  private OptionalProperty<Integer> age;

  // relationships
  private OptionalProperty<City> city;
  private OptionalProperty<List<Job>> jobs;
}

We have our OptionalPropertyDeserializer registered to object mapper. It works fine for attributes but we have problem with relationships because our OptionalProperty is generic non-collection so it's skipped in JsonApiEntityModelDeserialize::convertToRepresentationModel (https://github.com/toedter/spring-hateoas-jsonapi/blob/master/lib/src/main/java/com/toedter/spring/hateoas/jsonapi/JsonApiEntityModelDeserializer.java#L84).

If OptionalProperty works for you I can help with implementation to library (also with any other solution).

Thank you!

Adam

toedter commented 2 years ago

Thanks for asking, I will think about it.

toedter commented 2 years ago

sorry for not reacting in time, I am a little busy right now. I did not forget it and will try to respond before Christmas.

sadam21 commented 2 years ago

OK, thank you!

toedter commented 2 years ago

Hi Adam,

to get the information for the relationships, you can use the @JsonApiRelationships annotation, like

@Data
@Entity
@NoArgsConstructor
class Person {
  @Id
  @GeneratedValue
  @JsonIgnore
  private Long id;

  //attributes
  private String name;
  private Integer age;

  // relationships
  @OneToOne
  @JsonApiRelationships("city")
  @JsonIgnore
  private City city;

  @OneToMany
  @JsonApiRelationships("jobs")
  @JsonIgnore
  private List<Job> jobs;

Then a person can be serialized to (you have to use the builder for serialization)

{
    "data": {
        "type": "persons",
        "attributes": {
            "name": "Kai",
            "age": 56
        },
        "relationships": {
            "city": {
                "data": {
                    "id": "2",
                    "type": "cities"
                }
            },
            "jobs": {
                "data": [
                    {
                        "id": "1",
                        "type": "jobs"
                    }
                ]
            }
        }
    }
}

If you want to patch a person, send a PATCH request like

{
    "data": {
        "type": "persons",
        "attributes": {
            "name": "Kay",
            "age": 57
        },
        "relationships": {
            "city": {
                "data": {
                    "id": "4",
                    "type": "cities"
                }
            },
            "jobs": {
                "data": [
                    {
                        "id": "7",
                        "type": "jobs"
                    }
                ]
            }
        }
    }
}

If you want to distinguish between a value that should explicitly be set to null and a value that is not set at all in the patch body, one solution could be to use a special marker identifier like <<<null>>> in your patch body. Then your controller could react to this value and update the DTO's value to null.

I have created a little demo with your example (including serialization and patch method), you can download it at https://my.hidrive.com/share/ylcmjtbywn Password is patch-demo

Please re-open if you have further questions or contact me via email kai@toedter.com.