|
1 |
| -# Architectual overview of serializers and deserializers |
2 | 1 |
|
3 |
| -The main change is that now serializers and deserializers are split into |
4 |
| -- base serializers (deserializers) that contain building (parsing) logic shared by server and client side implementations |
5 |
| -- server and client serializers (deserializers) that are responsible for any additional building (parsing) logic unique to their implementations. |
| 2 | +# Serialization |
6 | 3 |
|
7 |
| -In deserialization, some parts are relevant only for client-side parsing whereas others are only for server-side parsing. for example, a server deserializer will never have to deal with a `included` object list. Similarly, in serialization, a client serializer will for example never ever have to populate any other top-level members than the primary data (like `meta`, `included`). These are examples of implementation-specific parsing/building for which the responsibility is moved to the corresponding implementation. |
| 4 | +The main change for serialization is that we have split the serialization responsibilities into two parts: |
8 | 5 |
|
9 |
| -Throughout the document and the code when referring to fields, members, object types, the technical language of json:api spec is used. At the core of (de)serialization is the |
| 6 | +* **Response (de)serializers** - (de)Serialization regarding serving or interpreting a response. |
| 7 | +* **Request (de)serializer** - (de)Serialization regarding creating or interpreting a request. |
| 8 | + |
| 9 | +This split is done because during deserialization, some parts are relevant only for *client*-side parsing whereas others are only for *server*-side parsing. for example, a server deserializer will never have to deal with a `included` object list. Similarly, in serialization, a client serializer will for example never ever have to populate any other top-level members than the primary data (like `meta`, `included`). |
| 10 | + |
| 11 | +Throughout the document and the code when referring to fields, members, object types, the technical language of json:api spec is used. At the core of (de)serialization is the |
10 | 12 | `Document` class, [see document spec](https://jsonapi.org/format/#document-structure).
|
11 | 13 |
|
12 |
| -## Deserialization |
| 14 | +## Changes |
| 15 | + |
| 16 | +In this section we will detail the changes made to the (de)serialization compared to the previous version. |
| 17 | + |
| 18 | +### Deserialization |
| 19 | + |
13 | 20 | The previous `JsonApiDeSerializer` implementation is now split into a `RequestDeserializer` and `ResponseDeserializer`. Both inherit from `BaseDocumentParser` which does the shared parsing.
|
14 | 21 |
|
15 | 22 | #### BaseDocumentParser
|
16 |
| -Responsible for |
17 |
| -- Converting the serialized string content into an intance of the `Document` class. |
18 |
| -- Building instances of the corresponding resource class (eg `Article`) by going through the document's primary data (`Document.Data`, [see primary data spec](https://jsonapi.org/format/#document-top-level)). |
19 | 23 |
|
20 |
| -Responsibility of any implementation-specific parsing is shifted through the abstract `BaseDocumentParser.AfterProcessField()` method. This method is fired once each time after a `AttrAttribute` or `RelationshipAttribute` is processed. It allows a implementation of `BaseDocumentParser` to intercept the parsing and add steps that are only required for clients/servers. |
| 24 | +This (base) class is responsible for: |
| 25 | + |
| 26 | +* Converting the serialized string content into an intance of the `Document` class. Which is the most basic version of JSON API which has a `Data`, `Meta` and `Included` property. |
| 27 | +* Building instances of the corresponding resource class (eg `Article`) by going through the document's primary data (`Document.Data`) For the spec for this: [Document spec](https://jsonapi.org/format/#document-top-level). |
| 28 | + |
| 29 | +Responsibility of any implementation the base class-specific parsing is shifted through the abstract `BaseDocumentParser.AfterProcessField()` method. This method is fired once each time after a `AttrAttribute` or `RelationshipAttribute` is processed. It allows a implementation of `BaseDocumentParser` to intercept the parsing and add steps that are only required for new implementations. |
21 | 30 |
|
22 | 31 | #### ResponseDeserializer
|
23 |
| -The client deserializer complements the base deserialization by |
24 |
| -* overriding the `AfterProcessField` method which takes care of the Included section |
25 |
| - * after a relationship was deserialized, it finds the appended included object and adds it attributs and (nested) relationships |
| 32 | + |
| 33 | +The client deserializer complements the base deserialization by |
| 34 | + |
| 35 | +* overriding the `AfterProcessField` method which takes care of the Included section \* after a relationship was deserialized, it finds the appended included object and adds it attributs and (nested) relationships |
26 | 36 | * taking care of remaining top-level members. that are only relevant to a client-side parser (meta data, server-side errors, links).
|
27 | 37 |
|
28 | 38 | #### RequestDeserializer
|
| 39 | + |
29 | 40 | For server-side parsing, no extra parsing needs to be done after the base deserialization is completed. It only needs to keep track of which `AttrAttribute`s and `RelationshipAttribute`s were targeted by a request. This is needed for the internals of JADNC (eg the repository layer).
|
| 41 | + |
30 | 42 | * The `AfterProcessField` method is overriden so that every attribute and relationship is registered with the `ITargetedFields` service after it is processed.
|
31 | 43 |
|
32 | 44 | ## Serialization
|
33 |
| -Like with the deserializers, `JsonApiSerializer` is now split up into a `ResponseSerializer` and `RequestSerializer`. Both inherit from a shared `BaseDocumentBuilder` class. Additionally, `BaseDocumentBuilder` inherits from `ResourceObjectBuilder`, which is extended by `IncludedResourceObjectBuilder`. |
| 45 | + |
| 46 | +Like with the deserializers, `JsonApiSerializer` is now split up into these classes (indentation implies hierarchy/extending): |
| 47 | + |
| 48 | +* `IncludedResourceObjectBuilder` |
| 49 | + |
| 50 | +* `ResourceObjectBuilder` - *abstract* |
| 51 | + * `DocumentBuilder` - *abstract* - |
| 52 | + * `ResponseSerializer` |
| 53 | + * `RequestSerializer` |
34 | 54 |
|
35 | 55 | ### ResourceObjectBuilder
|
| 56 | + |
36 | 57 | At the core of serialization is the `ResourceObject` class [see resource object spec](https://jsonapi.org/format/#document-resource-objects).
|
37 | 58 |
|
38 |
| -ResourceObjectBuilder is responsible for |
39 |
| -- Building a `ResourceObject` from an entity given a list of `AttrAttribute`s and `RelationshipAttribute`s. |
40 |
| - - Note: the resource object builder is NOT responsible for figuring out which attributes and relationships should be included in the serialization result, because this differs depending on an the implementation being client or server side. |
41 |
| - Instead, it is provided with the list. |
| 59 | +ResourceObjectBuilder is responsible for Building a `ResourceObject` from an entity given a list of `AttrAttribute`s and `RelationshipAttribute`s. - Note: the resource object builder is NOT responsible for figuring out which attributes and relationships should be included in the serialization result, because this differs depending on an the implementation being client or server side. Instead, it is provided with the list. |
42 | 60 |
|
43 | 61 | Additionally, client and server serializers also differ in how relationship members ([see relationship member spec](https://jsonapi.org/format/#document-resource-object-attributes) are formatted. The responsibility for handling this is again shifted, this time by virtual `ResourceObjectBuilder.GetRelationshipData()` method. This method is fired once each time a `RelationshipAttribute` is processed, allowing for additional serialization (like adding links or metadata).
|
44 | 62 |
|
45 | 63 | This time, the `GetRelationshipData()` method is not abstract, but virtual with a default implementation. This default implementation is to just create a `RelationshipData` with primary data (like `{"related-foo": { "data": { "id": 1" "type": "foobar"}}}`). Some implementations (server, included builder) need additional logic, others don't (client).
|
46 | 64 |
|
47 | 65 | ### BaseDocumentBuilder
|
48 | 66 | Responsible for
|
49 |
| -- Calling the base resource object serialization for one (or many) entities and wrapping the result in a `Document`. |
| 67 | + |
| 68 | +- Calling the base resource object serialization for one (or many) entities and wrapping the result in a `Document`. |
50 | 69 |
|
51 | 70 | Thats all. It does not figure out which attributes or relationships are to be serialized.
|
52 | 71 |
|
53 | 72 | ### RequestSerializer
|
| 73 | + |
54 | 74 | Responsible for figuring out which attributes and relationships need to be serialized and calling the base document builder with that.
|
55 | 75 | For example:
|
56 |
| -- for a POST request, this is often (almost) all attributes. |
57 |
| -- for a PATCH request, this is usually a small subset of attributes. |
| 76 | + |
| 77 | +- for a POST request, this is often (almost) all attributes. |
| 78 | +- for a PATCH request, this is usually a small subset of attributes. |
58 | 79 |
|
59 | 80 | Note that the client serializer is relatively skinny, because no top-level data (included, meta, links) will ever have to be added anywhere in the document.
|
60 | 81 |
|
61 | 82 | ### ResponseSerializer
|
| 83 | + |
62 | 84 | Responsible for figuring out which attributes and relationships need to be serialized and calling the base document builder with that.
|
63 | 85 | For example, for a GET request, all attributes are usually included in the output, unless
|
64 |
| -- Sparse field selection was applied in the client request |
65 |
| -- Runtime attribute hiding was applied, see [JADNC docs](https://json-api-dotnet.github.io/JsonApiDotNetCore/usage/resources/resource-definitions.html#runtime-attribute-filtering) |
| 86 | + |
| 87 | +* Sparse field selection was applied in the client request |
| 88 | +* Runtime attribute hiding was applied, see [JADNC docs](https://json-api-dotnet.github.io/JsonApiDotNetCore/usage/resources/resource-definitions.html#runtime-attribute-filtering) |
66 | 89 |
|
67 | 90 | The server serializer is also responsible for adding top-level meta data and links and appending included relationships. For this the `GetRelationshipData()` is overriden:
|
68 |
| -- it adds links to the `RelationshipData` object (if configured to do so, see `ILinksConfiguration`). |
69 |
| -- it checks if the processed relationship needs to be enclosed in the `included` list. If so, it calls the `IIncludedResourceObjectBuilder` to take care of that. |
70 | 91 |
|
| 92 | +* it adds links to the `RelationshipData` object (if configured to do so, see `ILinksConfiguration`). |
| 93 | +* it checks if the processed relationship needs to be enclosed in the `included` list. If so, it calls the `IIncludedResourceObjectBuilder` to take care of that. |
71 | 94 |
|
72 | 95 | ### IncludedResourceObjectBuilder
|
73 | 96 | Responsible for building the *included member* of a `Document`. Note that `IncludedResourceObjectBuilder` extends `ResourceObjectBuilder` and not `BaseDocumentBuilder` because it does not need to build an entire document but only resource objects.
|
74 | 97 |
|
75 |
| -Relationship *inclusion chains* are at the core of building the included member. For example, consider the request `articles?included=author.blogs.reviewers.favorite-food,reviewer.blogs.author.favorite-song`. It contains the following (complex) inclusion chains: |
| 98 | +Responsible for building the _included member_ of a `Document`. Note that `IncludedResourceObjectBuilder` extends `ResourceObjectBuilder` and not `DocumentBuilder` because it does not need to build an entire document but only resource objects. |
| 99 | + |
| 100 | +Relationship _inclusion chains_ are at the core of building the included member. For example, consider the request `articles?included=author.blogs.reviewers.favorite-food,reviewer.blogs.author.favorite-song`. It contains the following (complex) inclusion chains: |
| 101 | + |
76 | 102 | 1. `author.blogs.reviewers.favorite-food`
|
77 | 103 | 2. `reviewer.blogs.author.favorite-song`
|
78 | 104 |
|
79 | 105 | Like with the `RequestSerializer` and `ResponseSerializer`, the `IncludedResourceObjectBuilder` is responsible for calling the base resource object builder with the list of attributes and relationships. For this implementation, these lists depend strongly on the inclusion chains. The above complex example demonstrates this (note: in this example the relationships `author` and `reviewer` are of the same resource `people`):
|
80 |
| -- people that were included as reviewers from inclusion chain (1) should come with their `favorite-food` included, but not those from chain (2) |
81 |
| -- people that were included as authors from inclusion chain (2) should come with their `favorite-song` included, but not those from chain (1). |
82 |
| -- a person that was included as both an reviewer and author (i.e. targeted by both chain (1) and (2)), both `favorite-food` and `favorite-song` need to be present. |
83 | 106 |
|
84 |
| -To achieve this all of this, the `IncludedResourceObjectBuilder` needs to recursively parse an inclusion chain and make sure it does not append the same included more than once. This strategy is different from that of the ResponseSerializer, and for that reason it is a separate service. |
| 107 | +* people that were included as reviewers from inclusion chain (1) should come with their `favorite-food` included, but not those from chain (2) |
| 108 | +* people that were included as authors from inclusion chain (2) should come with their `favorite-song` included, but not those from chain (1). |
| 109 | +* a person that was included as both an reviewer and author (i.e. targeted by both chain (1) and (2)), both `favorite-food` and `favorite-song` need to be present. |
85 | 110 |
|
| 111 | +To achieve this all of this, the `IncludedResourceObjectBuilder` needs to recursively parse an inclusion chain and make sure it does not append the same included more than once. This strategy is different from that of the ResponseSerializer, and for that reason it is a separate service. |
0 commit comments