Skip to content

Commit ff99353

Browse files
author
Bart Koelman
authored
Changes usage of fields parameter to be json:api spec compliant (#904)
* Changes the use of fields query string parameter to be json:api spec compliant. Before we'd accept a relationship path between the square brackets and would only allow attribute names in the value. Now we expect a resource type between square brackets and the value can contain both attributes and relationships. * Fixed: pass ID attribute in ResourceDefinition callback for relationships.
1 parent 0aa077b commit ff99353

File tree

45 files changed

+829
-284
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+829
-284
lines changed

benchmarks/Query/QueryParserBenchmarks.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ public QueryParserBenchmarks()
3131

3232
var request = new JsonApiRequest
3333
{
34-
PrimaryResource = resourceGraph.GetResourceContext(typeof(BenchmarkResource))
34+
PrimaryResource = resourceGraph.GetResourceContext(typeof(BenchmarkResource)),
35+
IsCollection = true
3536
};
3637

3738
_queryStringReaderForSort = CreateQueryParameterDiscoveryForSort(resourceGraph, request, options, _queryStringAccessor);
@@ -56,6 +57,7 @@ private static QueryStringReader CreateQueryParameterDiscoveryForAll(IResourceGr
5657
{
5758
var resourceFactory = new ResourceFactory(new ServiceContainer());
5859

60+
var includeReader = new IncludeQueryStringParameterReader(request, resourceGraph, options);
5961
var filterReader = new FilterQueryStringParameterReader(request, resourceGraph, resourceFactory, options);
6062
var sortReader = new SortQueryStringParameterReader(request, resourceGraph);
6163
var sparseFieldSetReader = new SparseFieldSetQueryStringParameterReader(request, resourceGraph);
@@ -65,7 +67,7 @@ private static QueryStringReader CreateQueryParameterDiscoveryForAll(IResourceGr
6567

6668
var readers = new List<IQueryStringParameterReader>
6769
{
68-
filterReader, sortReader, sparseFieldSetReader, paginationReader, defaultsReader, nullsReader
70+
includeReader, filterReader, sortReader, sparseFieldSetReader, paginationReader, defaultsReader, nullsReader
6971
};
7072

7173
return new QueryStringReader(options, queryStringAccessor, readers, NullLoggerFactory.Instance);
@@ -92,7 +94,7 @@ public void DescendingSort()
9294
[Benchmark]
9395
public void ComplexQuery() => Run(100, () =>
9496
{
95-
var queryString = $"?filter[{BenchmarkResourcePublicNames.NameAttr}]=abc,eq:abc&sort=-{BenchmarkResourcePublicNames.NameAttr}&include=child&page[size]=1&fields={BenchmarkResourcePublicNames.NameAttr}";
97+
var queryString = $"?filter[{BenchmarkResourcePublicNames.NameAttr}]=abc,eq:abc&sort=-{BenchmarkResourcePublicNames.NameAttr}&include=child&page[size]=1&fields[{BenchmarkResourcePublicNames.Type}]={BenchmarkResourcePublicNames.NameAttr}";
9698

9799
_queryStringAccessor.SetQueryString(queryString);
98100
_queryStringReaderForAll.ReadAll(null);

docs/internals/queries.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@ To get a sense of what this all looks like, let's look at an example query strin
3434
filter=has(articles)&
3535
sort=count(articles)&
3636
page[number]=3&
37-
fields=title&
37+
fields[blogs]=title&
3838
filter[articles]=and(not(equals(author.firstName,null)),has(revisions))&
3939
sort[articles]=author.lastName&
4040
fields[articles]=url&
4141
filter[articles.revisions]=and(greaterThan(publishTime,'2001-01-01'),startsWith(author.firstName,'J'))&
4242
sort[articles.revisions]=-publishTime,author.lastName&
43-
fields[articles.revisions]=publishTime
43+
fields[revisions]=publishTime
4444
```
4545

4646
After parsing, the set of scoped expressions is transformed into the following tree by `QueryLayerComposer`:
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
curl -s -f http://localhost:14141/api/books?fields=publishYear
1+
curl -s -f http://localhost:14141/api/books?fields%5Bbooks%5D=publishYear
Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
# Sparse Fieldset Selection
22

3-
As an alternative to returning all attributes from a resource, the `fields` query string parameter can be used to select only a subset.
4-
This can be used on the resource being requested, as well as nested endpoints and/or included resources.
3+
As an alternative to returning all fields (attributes and relationships) from a resource, the `fields[]` query string parameter can be used to select a subset.
4+
Put the resource type to apply the fieldset on between the brackets.
5+
This can be used on the resource being requested, as well as on nested endpoints and/or included resources.
56

67
Top-level example:
78
```http
8-
GET /articles?fields=title,body HTTP/1.1
9+
GET /articles?fields[articles]=title,body,comments HTTP/1.1
910
```
1011

1112
Nested endpoint example:
1213
```http
13-
GET /api/blogs/1/articles?fields=title,body HTTP/1.1
14+
GET /api/blogs/1/articles?fields[articles]=title,body,comments HTTP/1.1
1415
```
1516

17+
When combined with the `include` query string parameter, a subset of related fields can be specified too.
18+
1619
Example for an included HasOne relationship:
1720
```http
18-
GET /articles?include=author&fields[author]=name HTTP/1.1
21+
GET /articles?include=author&fields[authors]=name HTTP/1.1
1922
```
2023

2124
Example for an included HasMany relationship:
@@ -25,9 +28,12 @@ GET /articles?include=revisions&fields[revisions]=publishTime HTTP/1.1
2528

2629
Example for both top-level and relationship:
2730
```http
28-
GET /articles?include=author&fields=title,body&fields[author]=name HTTP/1.1
31+
GET /articles?include=author&fields[articles]=title,body,author&fields[authors]=name HTTP/1.1
2932
```
3033

34+
Note that in the last example, the `author` relationship is also added to the `articles` fieldset, so that the relationship from article to author is returned.
35+
When omitted, you'll get the included resources returned, but without full resource linkage (as described [here](https://jsonapi.org/examples/#sparse-fieldsets)).
36+
3137
## Overriding
3238

3339
As a developer, you can force to include and/or exclude specific fields as [described previously](~/usage/resources/resource-definitions.md).

docs/usage/resources/attributes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ This can be overridden per attribute.
3939

4040
### Viewability
4141

42-
Attributes can be marked to allow returning their value in responses. When not allowed and requested using `?fields=`, it results in an HTTP 400 response.
42+
Attributes can be marked to allow returning their value in responses. When not allowed and requested using `?fields[]=`, it results in an HTTP 400 response.
4343

4444
```c#
4545
public class User : Identifiable

docs/usage/resources/resource-definitions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ from Entity Framework Core `IQueryable` execution.
1818

1919
### Excluding fields
2020

21-
There are some cases where you want attributes conditionally excluded from your resource response.
21+
There are some cases where you want attributes (or relationships) conditionally excluded from your resource response.
2222
For example, you may accept some sensitive data that should only be exposed to administrators after creation.
2323

2424
Note: to exclude attributes unconditionally, use `[Attr(Capabilities = ~AttrCapabilities.AllowView)]`.

docs/usage/writing/creating.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,10 @@ POST /articles HTTP/1.1
6161

6262
# Response body
6363

64-
POST requests can be combined with query string parameters that are normally used for reading data, such as `include` and `fields`. For example:
64+
POST requests can be combined with query string parameters that are normally used for reading data, such as `include` and `fields[]`. For example:
6565

6666
```http
67-
POST /articles?include=owner&fields[owner]=firstName HTTP/1.1
67+
POST /articles?include=owner&fields[people]=firstName HTTP/1.1
6868
6969
{
7070
...

docs/usage/writing/updating.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,10 @@ By combining the examples above, both attributes and relationships can be update
6969

7070
## Response body
7171

72-
PATCH requests can be combined with query string parameters that are normally used for reading data, such as `include` and `fields`. For example:
72+
PATCH requests can be combined with query string parameters that are normally used for reading data, such as `include` and `fields[]`. For example:
7373

7474
```http
75-
PATCH /articles/1?include=owner&fields[owner]=firstName HTTP/1.1
75+
PATCH /articles/1?include=owner&fields[people]=firstName HTTP/1.1
7676
7777
{
7878
...

src/Examples/JsonApiDotNetCoreExample/Models/Passport.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
using System;
2-
using System.Collections.Generic;
32
using System.ComponentModel.DataAnnotations.Schema;
4-
using System.Linq;
53
using JsonApiDotNetCore.Resources;
64
using JsonApiDotNetCore.Resources.Annotations;
75
using JsonApiDotNetCoreExample.Data;

src/JsonApiDotNetCore/Errors/ResourcesInRelationshipsNotFoundException.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System;
21
using System.Collections.Generic;
32
using System.Linq;
43
using System.Net;

0 commit comments

Comments
 (0)