Complex DTOs for GET (beyond JSV / XML) - Best Practices

I’ve searched around and gotten pieces from here and there on the topic. I’m hoping this thread can serve as the be-all-end-all thread for the discussion, for myself and for others.

For reference, I’ve looked here: Passing complex DTO in Postman
And here: Swagger ui and complex types

But this SO thread is the most on point for what I’m trying to do: https://stackoverflow.com/questions/22412886/complex-array-in-servicestack-request

Namely, I have a complex request DTO that has an array of FilterItem. I know I can do Filters=[{JSV}, {JSV}], etc. But that, to me, leaks implementation.

I’d rather use more “standard” querystring formats, like MVC. I know there are a few flavors of that for working with arrays and complex objects, but let’s just assume the MVC flavor.

Is there any way to FormData-style naming conventions in the URL instead? filters[0].prop1=…&filters[0].prop2=…&filters[1].prop1=…

I’m also looking at using POST with X-HTTP-Method-Override: GET as an alternative. Not sure how this will translate to JSON clients and such, though. I’m not even sure how JSON clients handles

Looking at swagger docs, looks like there’s some support for serialization in the URL (particularly, deepObject), but I don’t see array serialization, just object serialization. https://swagger.io/docs/specification/serialization/

So basically, I’ve researched this up and down. For ServiceStack or otherwise, I can’t find a best practices way of handling things, and the only way I’ve found to do it SS-specific is to use JSV or a custom serializer, which I don’t love.

How SHOULD this feature be implemented? Should we not really use complex objects in GETs? How should we do it instead?

Here’s my sample code for reference:

    [Tag("Assets")]
    [Route("/assets/master-grid/query", HttpMethods.Post)]
    public class QueryAssetMasterGrid : RequestWithFiltersBase, IPost, IPagedRequest, IReturn<PagedDataTableResponse>
    {
        public int? PageIndex { get; set; }
        public int? PageSize { get; set; }
        public List<string> SelectFields { get; set; }
        public List<string> GroupFields { get; set; }
        public List<QueryField> AggregationFields { get; set; }
        public List<OrderByQueryField> OrderByFields { get; set; }
    }
    public enum AggregationType
    {
        SUM,
        COUNT,
        AVG,
        MIN,
        MAX,
    }

    public class OrderByQueryField
        : QueryField
    {
        public bool Descending { get; set; }
    }

    public class QueryField
    {
        public string Field { get; set; }
        public AggregationType? Aggregator { get; set; }
        public string Alias { get; set; }
        public bool HasAlias { get; set; }
        public bool IsAggregate { get; set; }
        public string AliasOrField { get; set; }
    }

Right, it should be avoided as once you include complex object graphs in GET requests you’re going to be reliant on concrete implementations. Not really sure why you’re concerned that other FX’s don’t support JSV, if interoperability is an objective you wouldn’t use complex object graphs in Query Strings.

You can either use a POST Request, implement a custom request binder or read from the QueryString from within your Service implementation to parse the API request.

So my point was, isn’t there a standard or standard’ish way of handling complex data types in APIs? I’ve seen one of a few flavors, all of which using standard URL encoding with array keys. Examples:
?ids[]=1&ids[]=2&ids[]=3
?ids=1,2,3
?ids[0]=1&ids[1]=2&ids[2]=3

If an API had one of those, that would feel “normal.”

Similarly,
?filters[0].name=hello&filters[1].name=world

That feels pretty normal

?filters[key]=value&filters[otherKey]=otherValue

Still good.

?filters=[{key:value,otherKey:otherValue}] feels quite foreign.

Another example. jQuery.param takes this object:
var obj = { a: [{ key: "value", key2: "value2" }], b: [1,2,3,4,5] }

And converts it to this:
a[0][key]=value&a[0][key2]=value2&b[]=1&b[]=2&b[]=3&b[]=4&b[]=5

And there’s this implementation for node (from https://stackoverflow.com/a/58726527/1795053)

const qs = require('querystring');
let str = qs.stringify(obj)

My point being, there are some standard ways of looking at complex objects as simple query params. The flavors may be slightly different between implementations, but most frameworks support some flavor. So… is there a flavor for SS? For example, is there a way to support FormData-style binding? MVC model binding style?

Or… am I missing the point and still thinking about this the wrong way?

ServiceStack only supports complex object graphs in QueryString or FormData key value pairs using JSV, if you want to support a different strategy you’ll need to implement it.