title: Recommended pattern to implement JSONAPI filter with query params in Ember?
body: <p>I spent a chunk of time yesterday trying to include filter (reflecting the <a href="http://jsonapi.org/recommendations/#filtering" rel="nofollow noreferrer">JSONAPI spec</a>) in the query params of part of an Ember app. With Ember Data it is easy enough to pass a filter array to an endpoint, the problem I have is reflecting that filter array in the query params for a particular route. Note: other, non array, query params are working fine.</p>
  
  <p>TL;DR I have tried various options without success and have a solution that really feels unsatisfactory and not at all DRY. I figure that many others must have tackled this problem and have surely found a better solution. Read on for details of what I have tried so far.</p>
  
  <p>I started with something like this (I initially assumed it would work having read the <a href="https://guides.emberjs.com/release/routing/query-params/" rel="nofollow noreferrer">Ember docs on query params</a>):</p>
  
  <p>Controller:</p>
  
  <pre><code>import Controller from '@ember/controller';
  
  export default Controller.extend({
    queryParams: ['sort', 'filter'],
    sort: 'id',
  
    init() {
      this._super(...arguments);
      this.set('filter', []);
    },
  });
  </code></pre>
  
  <p>Route:</p>
  
  <pre><code>import Route from '@ember/routing/route';
  
  export default Route.extend({
    queryParams: {
      filter: {
        refreshModel: true
      },
      sort: {
        refreshModel: true
      }
    },
  
    model(params) {
      console.log(JSON.stringify(params)); // filter is always []
      return this.get('store').query('contact', params);
    }
  });
  </code></pre>
  
  <p>Acceptance Test (this was just a proof of concept test before I started on the more complex stuff):</p>
  
  <pre><code>  test('visiting /contacts with query params', async function(assert) {
      assert.expect(1);
      let done = assert.async();
  
      server.createList('contact', 10);
  
      server.get('/contacts', (schema, request) =&gt; {
        let params = request.queryParams;
  
        assert.deepEqual(
          params,
          {
            sort: '-id',
            "filter[firstname]": 'wibble'
          },
          'Query parameters are passed in as expected'
        );
        done();
        return schema.contacts.all();
      });
  
      await visit('/contacts?filter[firstname]=wibble&amp;sort=-id');
    });
  </code></pre>
  
  <p>No matter how I tweaked the above code, params.filter was always [] in the Route model function.</p>
  
  <p>I have searched around for best-practice on what would seem to be a common use case, but have not found anything recent. <a href="https://gist.github.com/machty/8167051" rel="nofollow noreferrer">sarus' solution here from Nov 2015</a> works, but means that every possible filter key has to be hardcoded in the controller and route, which seems far from ideal to me. Just imagine doing that for 20 possible filter keys! Using sarus' solution, here is code that works for the above acceptance test but as I say imagine having to hardcode 20+ potential filter keys:</p>
  
  <p>Controller:</p>
  
  <pre><code>import Controller from '@ember/controller';
  
  export default Controller.extend({
    queryParams: ['sort',
      { firstnameFilter: 'filter[firstname]' }
    ],
    sort: 'id',
    firstnameFilter: null,
  
    init() {
      this._super(...arguments);
    }
  });
  </code></pre>
  
  <p>Route:</p>
  
  <pre><code>import Route from '@ember/routing/route';
  
  export default Route.extend({
    queryParams: {
      firstnameFilter: {
        refreshModel: true
      },
      sort: {
        refreshModel: true
      }
    },
  
    model(params) {
      if (params.firstnameFilter) {
        params.filter = {};
        params.filter['firstname'] = params.firstnameFilter;
        delete params.firstnameFilter;
      }
      return this.get('store').query('contact', params);
    }
  });
  </code></pre>
  
  <p>I hope there's a better way!</p>