Definitions
Resolver
A resolver configuration defines how a schema will access the origin service. It provides elements that can map and
transform the inputs or args
coming from a GraphQL query, modify caching behaviors, and map and
transform the outputs, or results
of the query.
name
Type
string!
Description
The name of a resolver function to use. Broadly speaking, these resolvers respond to major categories of functionality in the underlying APIs. For a GraphQL APIs, use graphql:query
or graphql:mutation
. For REST APIs, select the resolver that matches the method you want to use. There are also several shapedb:*
resolvers which provide access to data stored in the built-in ShapeDB.
service
Type
string!
Description
The service connection to use when executing the resolver function. This should be a valid ID from the
services
object in your schema. The service connection will provide the base URL and any authentication details for
the request.
options
Type
object?
Description
The options
object provides configuration details for the resolver. The options vary depending upon the resolver in
use — see more in the documentation for the resolver you're using.
compose
Type
Resolver[]?
Description
Multiple resolver
s can also be composed, or sequenced in an array that acts like a pipeline. Read more in the section below.
args
Type
ParameterConfig?
Resolvers
Description
Configure operations to build the forwarded args
from the QueryContext
. args
does not support the serialize
prop
present on some other ParameterConfig
objects, and it must return an object.
Example
{
"args": {
"ops": [
{
"path": "page",
"mapping": "$args.page"
},
{
"path": "filter.status",
"mapping": "$args.status"
},
{
"path": "filter.name",
"mapping": [
["jsonPath", {"path": "$args.name"}],
["trim", {}]
]
}
]
}
}
let args = {
page: 1,
filter: {
status: 'alive',
name: 'Rick'
}
};
results
Type
ParameterConfig?
Resolvers
Description
Configure operations to build the results
or data returned from your resolver. results
does not support the
serialize
prop present on some other ParameterConfig
objects. Any valid JsonValue
may be returned from your ops.
Example
{
"results": {
"ops": [
{
"path": "id",
"mapping": "$finalResolver.data.id"
},
{
"path": "firstName",
"mapping": [
["jsonPath", {"path": "$finalResolver.data.name"}],
["replace", {"regexp": "[^\\s]+$", "replacement": ""}],
["trim", {}]
]
},
{
"path": "lastName",
"mapping": [
["jsonPath", {"path": "$finalResolver.data.name"}],
["replace", {"regexp": "^[^\\s]+", "replacement": ""}],
["trim", {}]
]
}
]
}
}
let result = {
id: '1',
firstName: 'Rick',
lastName: 'Sanchez'
};
headers
Type
ParameterConfig?
Resolvers
Description
Configures operations to build the headers
that are sent with your resolver request. The ops
must return an object,
with each top-level path being set as a single header on your request. Nested objects or arrays are not supported by the
simple
parameter style used here, so ensure all your values are represented as strings.
You cannot configure the authorization
header here, as that is set with encrypted data stored with your service
config.
Example
{
"headers": {
"ops": [
{
"path": "x-user-id",
"mapping": "$claims.sub"
}
],
"serialize": {
"defaults": {
"style": "simple",
"explode": false
}
}
}
}
let headers = {
'x-user-id': 'my-user'
};
path
Type
(ParameterConfig | string)!
Resolvers
Description
Can be a string value, which will be appended as-is to the service endpoint.
Can also be a ParameterConfig
object. The path
config object contains a special serialize
property template
which is required and allows you to provide the template for replacement of your ops tokens. For instance, this
template, /characters/{id}
will replace {id}
with the value of the op with a path id
.
None of this parameter's styles support nested objects, so be sure your ops all return strings, or use content serialization. Your ops must return an object, where the top-level object paths will be the tokens you use in your path pattern.
Note that the ParameterConfig
path will be normalized to remove leading and trailing slashes, and will prevent
doubling up of slashes if you have a token that evaluates to nothing. You can set the options.trailingSlash
boolean to
true
to always add a trailing slash to the end of your URLs, or false
to remove one if it creeps in.
Example
{
"RickAndMorty_character": {
"resolver": {
"name": "rest:get",
"service": "rick-and-morty",
"path": "characters/5"
},
"args": {
"type": "object",
"properties": {
"id": {"type": "string"}
}
},
"shape": "RickAndMorty_Character"
}
}
{
"RickAndMorty_character": {
"resolver": {
"name": "rest:get",
"service": "rick-and-morty",
"path": {
"ops": [
{
"path": "id",
"mapping": "$args.id"
}
],
"serialize": {
"template": "/characters/{id}",
"defaults": {
"style": "simple",
"explode": false
}
}
}
},
"args": {
"type": "object",
"properties": {
"id": {"type": "string"}
}
},
"shape": "RickAndMorty_Character"
}
}
// URITemplate - /characters/{id}
let path = '/characters/5';
searchParams
Type
ParameterConfig?
Resolvers
Description
Configures operations to build the searchParams
string in your query. If your results are defined, non-null, and not
an array or object, they will be parsed by qs.parse
and encoded according to your config. If your ops return an array
or object they will be encoded and serialized.
Example
{
"RickAndMorty_characters": {
"resolver": {
"name": "rest:get",
"service": "rick-and-morty",
"path": "/characters",
"searchParams": {
"ops": [
{
"path": "$",
"mapping": "$args"
}
],
"serialize": {
"defaults": {
"style": "form",
"explode": true
}
}
}
},
"args": {
"type": "object",
"properties": {
"page": {"type": "number"},
"filter": {
"type": "object",
"properties": {
"status": {"type": "string"},
"name": {"type": "string"},
"species": {"type": "string"}
}
}
}
},
"shape": "RickAndMorty_Character"
}
}
In this example, note that we map the $args
directly to the root. This is because the serialization behavior flattens
the object properties, and places them beside the page
param.
let searchParams = '?page=1&status=alive&name=rick';
form
Type
ParameterConfig?
Resolvers
Description
Identical to searchParams
. Using form
will set your request Content-Type
headers to
application/x-www-form-urlencoded
.
body
Type
ParameterConfig?
Resolvers
Description
Use ops to build the body and then serialize it via a content type encoding. If the ops result in an undefined or null
value an empty string is set. If they result in a non-object, the toString()
method will be invoked. If an object is
returned it will be stringified depending on the content type set.
When using the body
property a header will not be set by default. Be sure to set the appropriate header to match your
serialization strategy.
Values
body.serialize.content.contentType
may be set to any of the following values to invoke specific serializers.
application/x-www-form-urlencoded
The op result will be passed into qs.stringify
.
body.serialize.content.options
is an object of options to pass to the function. See
qs.stringify for details.
application/json
The op result will be passed into JSON.stringify
.
text/csv
The op result will be passed into csv-stringify
.
body.serialize.content.options
is an object of options to pass to the function. See
csv-stringify for details.
json
Type
ParameterConfig?
Resolvers
Description
Configured ops will build your request object, and make a request with the Content-Type
header set to
application/json
(unless you override it).
ComposeResolver (Resolver[])
A single resolver will allow you to place a facade on your third-party API. This can be useful for wrapping up authentication concerns, decreasing complexity, and providing some of the inherent benefits of GraphQL. Resolvers can also be nested in an array under the compose
keyword on a resolver
(or @resolver
) property. When used this way, resolvers empower you to connect to multiple third-party services, manipulate data, and generate a completely query-able interface.
Example
This example is from our Auth0 + Stripe Starter. It use resolver composition to verify a record using a third-party auth token, then load a foreign key from that record, then use the key to query a service.
{
"getMySubscriptions": {
"shape": {
"type": "array",
"items": {
"@ref": "stripe:Subscription"
}
},
"resolver": {
"results": {
"ops": [
{
"path": "$",
"mapping": "$resolvers.subscriptions.data"
}
]
},
"compose": [
{
"if": "!isEmpty($claims.sub)",
"id": "profile",
"name": "shapedb:find",
"service": "shapedb",
"shapeName": "Profile",
"args": {
"ops": [
{
"path": "where.id.eq",
"mapping": "$claims.sub"
}
]
}
},
{
"if": "!isEmpty($previousResolver.stripeCustomerId)",
"id": "subscriptions",
"name": "rest:get",
"service": "stripe",
"shapeName": "/v1/subscriptions",
"searchParams": {
"ops": [
{
"path": "expand",
"op": "extend",
"value": "data.items"
},
{
"path": "expand",
"op": "extend",
"value": "data.plan.product"
},
{
"path": "expand",
"op": "extend",
"value": "data.latest_invoice.payment_intent"
},
{
"path": "customer",
"mapping": "$resolvers.profile.stripeCustomerId"
}
],
"serialize": {
"defaults": {
"style": "deepObject",
"explode": true
}
}
}
}
]
},
"description": "Get my subscriptions"
}
}
Notice above how the second resolver is conditional on the first resolver, using the handle $previousResolver
in the
if
statement. Notice also that a mapping is made in the second resolver using the id
handle defined on the first
resolver. Through mapping
and the QueryContext
you can pass data between the resolvers.
compose
Type
Resolver[]?
Description
The key to composition. There are resolver definitions in a nested array. They will be evaluated sequentially and in order.
compose[].id
Type
string?
Description
An optional id
, or handle, for the resolver. This allows access the resolver in later operations with a fixed, clear
identifier. For example, if a resolver has the id
userProfile
, then a later resolver will be able to access it's
results for mapping using $resolvers.userProfile
.
compose[].if
Type
ExpressionString?
Description
An optional expression to determine whether this resolver should run. Expression evaluation has access to the entire
QueryContext
and a fairly comprehensive suite of lodash.fp
functions.
If an expression evaluates to a truthy value, the resolver will run, otherwise it will be skipped. Skipped resolvers
will still set an index, for example, if the second resolver is skipped, $resolvers[1]
will still exist, and it's value will be null
.
results
Type
ParameterConfig?
Description
Optional operation config to map the results from the many resolvers to an object that matches the returned shape. If
results
is omitted then the output of the last resolver that executes will be returned.
Example
{
"RickAndMorty_characters": {
"resolver": {
"name": "graphql:query",
"service": "rick-and-morty",
"fieldName": "characters",
"args": {
"ops": [
{
"path": "$",
"mapping": "$args"
}
]
},
"results": {
"ops": [
{
"path": "$",
"mapping": "$finalResolver.results"
}
]
}
},
"args": {
"type": "object",
"properties": {
"page": {"type": "integer"},
"filter": {"@ref": "rick-and-morty:FilterCharacter"}
}
},
"shape": {
"type": "array",
"items": {
"@ref": "RickAndMorty_Character"
}
}
}
}
let results = [{
id: '1',
name: 'Rick Sanchez',
status: 'Alive'
}, {
id: '2',
name: 'Morty Smith',
status: 'Alive'
}, ...]
QueryResolver (Resolver)
Resolvers may appear on both queries / mutations and on the individual fields of shapes. In either case they use the same definition and general syntax.
$queries.{queryName}.resolver
$mutations.{queryName}.resolver
Type
Resolver!
Description
When a resolver is used in a query or mutation it is a required property at the root of the query object.
ShapeFieldResolver (Resolver)
When a resolver is used on a shape's field it is capable of providing resolved data whenever that shape, and that specific field is requested in a query. This can be helpful when you want to create data that has a strong association with a shape, like fetching data from a remote service.
$shapes.{shapeName}.schema.properties.{fieldName}.@resolver
Type
Resolver?
Description
The optional field resolver is identified using the property @resolver
.
ParameterConfig
The configuration object for a parameter.
ops
Type
ParameterOp[]!
Description
An array of ParameterOp
objects. The order of the ops will be obeyed even if they don't make sense. For instance, if a
$
root path is set late in the array it may overwrite all of the results of all the previous operations (unless you
use an extend
or concat
op).
serialize
Type
ParameterSerializeConfig[]!
Description
An object defining serialization behaviors for your parameters.
ParameterOp
Each ParameterOp
defines an operation to get and set a value at the given path
.
path
Type
string!
Description
The path
where a value should appear in the parameter output. In some cases this path will be accessible through a
token, for replacement (as in resolver.path.serialize.template
) and in other cases it refers to the key in something
like searchParams
or a form
body.
path
is defined using typical dot-and-brackets notation style, similar to what you'd find in
jsonpath-plus or
lodash.get. It includes jsonpath-plus
-inspired enhancements for working with
arrays.
Examples
Below are some examples of paths and the expected output.
Root Path
Using the special symbol $
you can set the root path. If you set the root path to a non-object, but have later ops with paths that require an object or array, it will be coerced to the correct value.
{
"path": "$",
"value": {"id": 123}
}
// /characters/{id}
let path = '/characters/123';
let searchParams = '?id=123';
let result = '{"data": {"id": 123}}';
Simple Path
Simple paths, by property name.
{
"path": "name",
"value": "Rick"
}
// URIPattern /characters/{name}
let path = '/characters/rick';
let searchParams = '?name=Rick';
let result = '{"data": {"name": "Rick"}}';
Deep Path
A path can set a deep property, implying that it's parent is an object.
{
"path": "character.name",
"value": "Rick"
}
// path URIPattern - /characters/{character} - results aren't useful
let path = '/characters/[Object object]';
let searchParams = '?character[name]=Rick';
let result = '{"data": {"character": {"name": "Rick"}}}';
Loop Path - All
The all iterator will iterate over arrays and the keys of an object. Provides the variable $loop in the context.
{
"path": "characters[*].name",
"mapping": [
["get": {"path": "$loop.key"}],
["prepend": {"text": "Rick"}]
]
}
let searchParams = '?characters[name]=Rick1&characters[name]=Rick2';
let result = '{"data": {"characters": [{"name": "Rick1"}, {"name": "Rick2"}] }}';
{
"path": "character",
"mapping": "$loop.key"
}
let searchParams = '?characters[name]=name&characters[status]=status';
let result = '{"data": {"characters": [{"name": "name"}, {"status": "status"}] }}';
Loop Path - Pluck
Similar to the JSONPath-plus implementation.
Comma-delimit indexes of the array to operate upon. In the example below, the first and third array members will have
their name
property overwritten. Provides the variable $loop in the context.
{
"path": "characters[0,2].firstName",
"mapping": [
["jsonPath", {"path": "$loop.item.name"}],
["replace", {"regexp": "[^\\s]+$", "replacement": ""}],
["trim", {}]
]
}
let searchParams = '?characters[firstName]=Rick&characters[firstName]=&characters[firstName]=Morty';
let result = '{"data": {"characters": [{"firstName": "Rick"}, {"firstName": ""} {"firstName": "Morty"}] }}';
Loop Path - Slice
Uses JSONPath-Plus style array syntax, which is itself derivative of Python slice syntax. In the example below the
colon-delimited positions indicate [start:stop:step]
. The positions start at 0
.
start
is where to begin the slice. Omitting it starts at the beginning, for example,[:5]
.stop
is where to end the slice. Omitting it will run from the start position to the end. for example,[2:]
.step
is the increment to grab the slice at. for example,[2:8:2]
will grab every second item, between the second and eighth index in the array.
{
"path": "letters[1:3:2]",
"mapping": [
["get": {"path": "$loop.item"}],
["prepend": {"text": "Z"}]
]
}
let searchParams = '?letters=A&letters=ZB&letters=C&letters=ZD&letters=E&letters=F';
let result = '{"data": {"letters": ["A", "ZB", "C", "ZD", "E", "F"] }}';
op
Type
string?
Description
The op
instructs the mapper on how to place the value from this operation.
Values
set
The default operation. If the same path appears twice in the operation array, the last value will be set over the earlier value.
{
"ops": [
{"path": "foo", "op": "set", "value": "FOO"},
{"path": "foo", "op": "set", "value": "BAR"}
]
}
let result = {
foo: 'BAR'
};
If a later value requires that an earlier set
operation be overwritten due to a type incompatibility, that will
happen:
{
"ops": [
{"path": "foo", "op": "set", "value": "FOO"},
{"path": "foo.bar", "op": "set", "value": "BAR"}
]
}
let result = {
foo: {
bar: 'BAR'
}
};
extend
An optional behavior which will extend an object value rather than overwriting it.
{
"ops": [
{"path": "foo", "op": "extend", "value": {"mighty": "MOUSE"}},
{"path": "foo", "op": "extend", "value": {"daffy": "DUCK"}}
]
}
let result = {
foo: {
mighty: 'MOUSE',
daffy: 'DUCK'
}
};
extend
will ignore non-object values if attempting to extend them
{
"ops": [
{"path": "foo", "op": "extend", "value": {"name": "Morty"}},
{"path": "foo", "op": "extend", "value": "Rick"}
]
}
let result = {
foo: {
name: 'Morty'
}
};
concat
An optional behavior which will concatenate a later value onto an earlier value at the same path
.
{
"ops": [
{"path": "foo", "op": "concat", "value": ["Rick"]},
{"path": "foo", "op": "concat", "value": ["Morty", "Beth"]}
]
}
let result = {
foo: ['Rick', 'Morty', 'Beth']
};
Non-array values will be pushed.
{
"ops": [
{"path": "foo", "op": "concat", "value": "Rick"},
{"path": "foo", "op": "concat", "value": "Morty"},
{"path": "foo", "op": "concat", "value": "Beth"}
]
}
let result = {
foo: ['Rick', 'Morty', 'Beth']
};
remove
An optional behavior which remove the value at the provided path
from the results of the operations.
{
"ops": [
{"path": "foo", "value": "Rick"},
{"path": "bar", "value": "Morty"},
{"path": "foo", "op": "remove"}
]
}
let result = {
bar: 'Morty'
};
value
Type
JsonValue?
Any value
set will be placed at the path
exactly as it exists in the schema, and then have the serializers
run on
it.
Can be any valid JsonValue
.
mapping
Type
string | DirectiveConfig?
Description
A mapping
string is shorthand for the jsonPath
directive. Alternatively, mapping
can be a full DirectiveConfig
which will work as a pipeline on your value, allowing you to get and transform a value from the QueryContext
.
Example
let context = {
$args: {
books: [
{
title: 'Little House on the Prairie'
},
{
title: 'Little Women'
},
{
title: 'Stuart Little'
}
],
storeName: 'Big Lots'
}
};
{
"ops": [
{"path": "readingList", "mapping": "$args.books[0,2]"},
{
"path": "storeAbbrev",
"mapping": [
["jsonPath", {"path": "$finalResolver.data.name"}],
["replace", {"regexp": "[^\\s]+$", "replacement": ""}],
["trim", {}]
]
}
]
}
let result = {
readingList: [
{
title: 'Little House on the Prairie'
},
{
title: 'Stuart Little'
}
],
storeAbbrev: 'Big'
};
ops (nested)
Type
ParameterOps[]?
Description
You have the ability to nest parameter operation lists into other operations. In the nested operations you have access
to the context variable $parent
.
The $parent
context contains the $value and $loop from the point at which it was created. That is
to say, if you were to replace a mapping
with nested ops
then the $value
and $loop
values from mapping
would
now be available at $parent.$value
and $parent.$loop
.
$parent
also contains the $parent
property for accessing grandparents, great-grandparents, etc. Accessing a
great-grandparent's $value
looks like this $parent.$parent.$parent.$value
.
This behavior is complex, so please see the examples below to see how it operates in practice. Notice the two properties resolve the same values even though they form the paths differently—one with a leading loop syntax, and the other with a trailing loop syntax. In the example with trailing loop syntax, $parent.$value
contains the same value as $parent.$loop.item
. Whichever style you choose will depend on your application and preferences.
Example
let context = {
$args: {
getProductList: {
items: [
{
name: 'Shirt',
related: [
{
title: 'how to wear a shirt'
},
{
title: 'shirts to wear'
}
],
attributes: [
{
inventory: 100,
sku: 'sku_1000'
},
{
inventory: 200,
sku: 'sku_2000'
}
]
},
{
name: 'Pants',
related: [
{
title: 'how to wear a pants'
},
{
title: 'pants to wear'
}
],
attributes: [
{
inventory: 50,
sku: 'sku_8000'
},
{
inventory: 60,
sku: 'sku_9000'
}
]
}
]
}
}
};
[
{
"path": "leadingLoop",
"mapping": "$args.getShopifyProductList.items"
},
{
"path": "leadingLoop",
"ops": [
{
"path": "[*].related",
"ops": [
{
"path": "[*].title",
"mapping": [
["get", {"path": "$value"}],
["toUpper", {}]
]
},
{
"path": "[*].parentProduct",
"mapping": "$parent.$loop.item.name"
}
]
},
{
"path": "[*].attributes",
"ops": [
{
"path": "[*].inventory",
"mapping": [
["get", {"path": "$value"}],
["toUpper", {}]
]
},
{
"path": "[*].sku",
"mapping": [
["get", {"path": "$value"}],
["replace", {"regexp": "^sku_", "replacement": ""}]
]
}
]
}
]
},
{
"path": "trailingLoop",
"mapping": "$args.getShopifyProductList.items"
},
{
"path": "trailingLoop[*]",
"ops": [
{
"path": "related[*]",
"ops": [
{
"path": "title",
"mapping": [
["get", {"path": "$value"}],
["toUpper", {}]
]
},
{
"path": "parentProduct",
"mapping": "$parent.$parent.$value.name"
}
]
},
{
"path": "attributes[*]",
"ops": [
{
"path": "inventory",
"mapping": [
["get", {"path": "$value"}],
["toUpper", {}]
]
},
{
"path": "sku",
"mapping": [
["get", {"path": "$value"}],
["replace", {"regexp": "^sku_", "replacement": ""}]
]
}
]
}
]
}
]
var result = {
leadingLoop: [
{
name: 'Shirt',
related: [
{title: 'HOW TO WEAR A SHIRT', parentProduct: 'Shirt'},
{title: 'SHIRTS TO WEAR', parentProduct: 'Shirt'}
],
attributes: [
{inventory: 100, sku: '1000'},
{inventory: 200, sku: '2000'}
]
},
{
name: 'Pants',
related: [
{title: 'HOW TO WEAR A PANTS', parentProduct: 'Pants'},
{title: 'PANTS TO WEAR', parentProduct: 'Pants'}
],
attributes: [
{inventory: 50, sku: '8000'},
{inventory: 60, sku: '9000'}
]
}
],
trailingLoop: [
{
name: 'Shirt',
related: [
{title: 'HOW TO WEAR A SHIRT', parentProduct: 'Shirt'},
{title: 'SHIRTS TO WEAR', parentProduct: 'Shirt'}
],
attributes: [
{inventory: 100, sku: '1000'},
{inventory: 200, sku: '2000'}
]
},
{
name: 'Pants',
related: [
{title: 'HOW TO WEAR A PANTS', parentProduct: 'Pants'},
{title: 'PANTS TO WEAR', parentProduct: 'Pants'}
],
attributes: [
{inventory: 50, sku: '8000'},
{inventory: 60, sku: '9000'}
]
}
]
};
ParameterSerializeConfig
This configuration object, when present, allows you to configure serializers. Serialization happens after the mapping operations, and will stringify and encode the keys and values according to the options you configure.
content
Type
ParameterSerializeContentOptions?
Description
Set a content-encoding for the operation result. Only applies to the body
parameter.
defaults
Type
ParameterSerializeContentOptions | ParameterSerializeStyleOptions?
Description
Set default serialize options for each parameter in your operation results. You may either set a content encoding, or use the style options
paths
Type
{[k: string]: ParameterSerializeContentOptions | ParameterSerializeStyleOptions}?
Description
A config object, defining top-level paths from the operation results to apply the specific serializer settings to. Will override the defaults you define above, and the internal, system defaults for a given parameter.
ParameterSerializeStyleOptions
Set style options for serializing and encoding the parameter. Modeled on the OpenAPIv3 spec.
style
Type
string!
Description
Style is one of the two basic styling options. Allows selection of a standardized style, per the OpenAPI spec.
explode
Type
boolean!
Description
Explode is one of the two basic styling options. Toggles a serialization behavior for objects and arrays, per the OpenAPI spec.
Values
None of the values in the matrix are percent-encoded for clarity when reading the table. By default they will be percent-encoded, per RFC 3986. See the encoding booleans in the next section for ways to configure the encoding behaviors.
Given
let primitive = 5;
let array = [3, 4, 5];
let object = {role: 'admin', firstName: 'Alex'};
Result
for | style | explode | path | primitive | array | object |
---|---|---|---|---|---|---|
path, headers | simple | false | id | 5 | 3,4,5 | role,admin,firstName,Alex |
path, headers | simple | true | id | 5 | 3,4,5 | role=admin,firstName=Alex |
path | label | false | id | 5 | .3,4,5 | .role,admin,firstName,Alex |
path | label | true | id | 5 | .3.4.5 | .role=admin.firstName=Alex |
path | matrix | false | id | 5 | ;id=3,4,5 | ;id=role,admin,firstName,Alex |
path | matrix | true | id | 5 | ;id=3;id=4;id=5 | ;role=admin;firstName=Alex |
searchParams, form | form | true | id | id=5 | id=3&id=4&id=5 | role=admin&firstName=Alex |
searchParams, form | form | false | id | id=5 | id=3,4,5 | id=role,admin,firstName,Alex |
searchParams, form | spaceDelimited | true | id | id=5 | id=3&id=4&id=5 | role=admin&firstName=Alex |
searchParams, form | spaceDelimited | false | id | id=5 | id=3 4 5 | id=role admin firstName Alex |
searchParams, form | pipeDelimited | true | id | id=5 | id=3&id=4&id=5 | role=admin&firstName=Alex |
searchParams, form | pipeDelimited | false | id | id=5 | id=3|4|5 | id=role|admin|firstName|Alex |
searchParams, form | deepObject | n/a | id | id=5 | id=3&id=4&id=5 | id[role]=admin&id[firstName]=Alex |
path, searchParams, form, headers | none† | n/a | id | 5 | 3,4,5 | [object Object] |
none
style simple applies toString()
to the value, skipping processing and encoding entirely