Skip to content

Commit 4f77ee1

Browse files
committed
Overall improvements to parser.
- Less exceptions caused by data type assumptions - Better handling of list data - Does not special-case RelationshipView
1 parent 0cfe2e1 commit 4f77ee1

File tree

1 file changed

+61
-23
lines changed

1 file changed

+61
-23
lines changed

rest_framework_json_api/parsers.py

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from rest_framework.exceptions import ParseError
66

77
from . import utils, renderers, exceptions
8+
from .serializers import ResourceIdentifierObjectSerializer
89

910

1011
class JSONParser(parsers.JSONParser):
@@ -57,6 +58,29 @@ def parse_metadata(result):
5758
def parse(self, stream, media_type=None, parser_context=None):
5859
"""
5960
Parses the incoming bytestream as JSON and returns the resulting data
61+
62+
There are two basic object types in JSON-API.
63+
64+
1. Resource Identifier Object
65+
66+
They only have 'id' and 'type' keys (optionally also 'meta'). The 'type'
67+
should be passed to the views for processing. These objects are used in
68+
'relationships' keys and also as the actual 'data' in Relationship URLs.
69+
70+
2. Resource Objects
71+
72+
They use the keys as above plus optional 'attributes' and
73+
'relationships'. Attributes and relationships should be flattened before
74+
sending to views and the 'type' key should be removed.
75+
76+
We support requests with list data. In JSON-API list data can be found
77+
in Relationship URLs where we would expect Resource Identifier Objects,
78+
but we will also allow lists of Resource Objects as the users might want
79+
to implement bulk operations in their custom views.
80+
81+
In addition True, False and None will be accepted as data and passed to
82+
views. In JSON-API None is a valid data for 1-to-1 Relationship URLs and
83+
indicates that the relationship should be cleared.
6084
"""
6185
result = super(JSONParser, self).parse(stream, media_type=media_type, parser_context=parser_context)
6286

@@ -65,38 +89,52 @@ def parse(self, stream, media_type=None, parser_context=None):
6589

6690
data = result.get('data')
6791

68-
from rest_framework_json_api.views import RelationshipView
69-
if isinstance(parser_context['view'], RelationshipView):
70-
# We skip parsing the object as JSONAPI Resource Identifier Object and not a regular Resource Object
71-
if isinstance(data, list):
72-
for resource_identifier_object in data:
73-
if not (resource_identifier_object.get('id') and resource_identifier_object.get('type')):
74-
raise ParseError(
75-
'Received data contains one or more malformed JSONAPI Resource Identifier Object(s)'
76-
)
77-
elif not (data.get('id') and data.get('type')):
78-
raise ParseError('Received data is not a valid JSONAPI Resource Identifier Object')
79-
92+
resource_name = utils.get_resource_name(parser_context)
93+
method = parser_context.get('request').method
94+
in_relationship_view = (
95+
parser_context['view'].serializer_class == ResourceIdentifierObjectSerializer
96+
)
97+
98+
if isinstance(data, list):
99+
for item in data:
100+
if not isinstance(item, dict):
101+
err = "Items in data array must be objects with 'id' and 'type' members."
102+
raise ParseError(err)
103+
104+
if in_relationship_view:
105+
for identifier in data:
106+
self.verify_resource_identifier(identifier)
107+
return data
108+
else:
109+
return [self.parse_resource(d, d, resource_name, method) for d in data]
110+
elif isinstance(data, dict):
111+
if in_relationship_view:
112+
self.verify_resource_identifier(data)
113+
return data
114+
else:
115+
return self.parse_resource(data, result, resource_name, method)
116+
else:
117+
# None, True, False, numbers and strings
80118
return data
81119

82-
request = parser_context.get('request')
83-
120+
def parse_resource(self, data, meta_source, resource_name, method):
84121
# Check for inconsistencies
85-
resource_name = utils.get_resource_name(parser_context)
86-
if data.get('type') != resource_name and request.method in ('PUT', 'POST', 'PATCH'):
122+
if data.get('type') != resource_name and method in ('PUT', 'POST', 'PATCH'):
87123
raise exceptions.Conflict(
88124
"The resource object's type ({data_type}) is not the type "
89-
"that constitute the collection represented by the endpoint ({resource_type}).".format(
90-
data_type=data.get('type'),
91-
resource_type=resource_name
92-
)
125+
"that constitute the collection represented by the endpoint ({resource_type})." \
126+
.format(data_type=data.get('type'), resource_type=resource_name)
93127
)
94-
if not data.get('id') and request.method in ('PATCH', 'PUT'):
95-
raise ParseError("The resource identifier object must contain an 'id' member")
128+
if not data.get('id') and method in ('PATCH', 'PUT'):
129+
raise ParseError("The resource object must contain an 'id' member.")
96130

97131
# Construct the return data
98132
parsed_data = {'id': data.get('id')} if 'id' in data else {}
99133
parsed_data.update(self.parse_attributes(data))
100134
parsed_data.update(self.parse_relationships(data))
101-
parsed_data.update(self.parse_metadata(result))
135+
parsed_data.update(self.parse_metadata(meta_source))
102136
return parsed_data
137+
138+
def verify_resource_identifier(self, data):
139+
if not data.get('id') or not data.get('type'):
140+
raise ParseError('Received data is not a valid JSONAPI Resource Identifier Object(s).')

0 commit comments

Comments
 (0)