Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom inflector with inversion? #851

Closed
ef4 opened this issue Jun 21, 2021 · 4 comments
Closed

Custom inflector with inversion? #851

ef4 opened this issue Jun 21, 2021 · 4 comments

Comments

@ef4
Copy link
Contributor

ef4 commented Jun 21, 2021

Upgrading to 0.17.beta, I need to reproduce the pluralization I had in 0.16 for (I think) JSONAPISerializers.ResourceTypePath and JSONAPISerializers.ResourceType. This nearly works:

import JSONAPISource, { JSONAPISerializers } from '@orbit/jsonapi';
import { buildSerializerSettingsFor } from '@orbit/serializers';

let inflectors = [
  'pluralize,'
  'dasherize',
];

new JSONAPISource(
  serializerSettingsFor: buildSerializerSettingsFor({
    settingsByType: {
      [JSONAPISerializers.ResourceTypePath]: {
        serializationOptions: {
          inflectors,
        },
      },
      [JSONAPISerializers.ResourceType]: {
        serializationOptions: {
          inflectors,
        },
      },
    },
  });
)

But the pluralization is naive, so I need to use buildInflector to extend it:

import { buildInflector } from '@orbit/serializers';
let inflectors = [
  buildInflector({ facility: 'facilities' }, (str) => `${str}s`),
  'dasherize',
];

And this works in the forward direction, but I lose inverse inflection that was working when I only had the default pluralize. Specifically, URLs and request bodies get pluralized, but then when deserializing the response I get "Model is not defined" errors because the JSON:API type in the response has not been singularized.

How can I get both custom pluralization and inversion?

@dgeb
Copy link
Member

dgeb commented Jun 22, 2021

@ef4 You are indeed very close. Here's a similar snippet I've shared on gitter:

import { buildSerializerSettingsFor, buildInflector } from '@orbit/serializers';

let JSONAPISettings = {
  serializerSettingsFor: buildSerializerSettingsFor({
    sharedSettings: {
      inflectors: {
        pluralize: buildInflector(
          { cow: 'kine', person: 'people' }, // custom mappings
          (input) => `${input}s` // naive pluralizer, specified as a fallback
        ),
        singularize: buildInflector(
          { kine: 'cow', people: 'person' }, // custom mappings
          (arg) => arg.substr(0, arg.length - 1) // naive singularizer, specified as a fallback
        )
      }
    },
    settingsByType: {
      [JSONAPISerializers.ResourceField]: {
        serializationOptions: { inflectors: ['dasherize'] }
      },
      [JSONAPISerializers.ResourceFieldParam]: {
        serializationOptions: { inflectors: ['dasherize'] }
      },
      [JSONAPISerializers.ResourceFieldPath]: {
        serializationOptions: { inflectors: ['dasherize'] }
      },
      [JSONAPISerializers.ResourceType]: {
        serializationOptions: { inflectors: ['pluralize', 'dasherize'] }
      },
      [JSONAPISerializers.ResourceTypePath]: {
        serializationOptions: { inflectors: ['pluralize', 'dasherize'] }
      }
    }
  })
};

The key here is that standard serializers know about certain inverse inflectors that are identified by name. The string serializer knows that the inverse of pluralize is singularize for instance.

By defining our custom inflector by name in sharedSettings, these can be shared across all serializers. And you can continue to reference those inflectors by name in your serializaitonOptions.

IMO one awkward piece here is the manual inversion of the custom mappings, which certainly could be handled in a more DRY and clever way.

@dgeb
Copy link
Member

dgeb commented Jun 22, 2021

To follow up, something along these lines keeps the custom mappings more DRY:

import { buildSerializerSettingsFor, buildInflector } from '@orbit/serializers';

const pluralizations = { cow: 'kine', person: 'people' };
const singularizations = Object.keys(pluralizations).reduce((inverse, k) => { inverse[pluralizations[k]] = k; return inverse; }, {});

let JSONAPISettings = {
  serializerSettingsFor: buildSerializerSettingsFor({
    sharedSettings: {
      inflectors: {
        pluralize: buildInflector(
          pluralizations,
          (input) => `${input}s` // naive pluralizer, specified as a fallback
        ),
        singularize: buildInflector(
          singularizations,
          (arg) => arg.substr(0, arg.length - 1) // naive singularizer, specified as a fallback
        )
      }
    },
    settingsByType: {
      [JSONAPISerializers.ResourceField]: {
        serializationOptions: { inflectors: ['dasherize'] }
      },
      [JSONAPISerializers.ResourceFieldParam]: {
        serializationOptions: { inflectors: ['dasherize'] }
      },
      [JSONAPISerializers.ResourceFieldPath]: {
        serializationOptions: { inflectors: ['dasherize'] }
      },
      [JSONAPISerializers.ResourceType]: {
        serializationOptions: { inflectors: ['pluralize', 'dasherize'] }
      },
      [JSONAPISerializers.ResourceTypePath]: {
        serializationOptions: { inflectors: ['pluralize', 'dasherize'] }
      }
    }
  })
};

@ef4
Copy link
Contributor Author

ef4 commented Jun 22, 2021

Thanks. This looks like it will solve my problem.

Totally as an aside, one of my favorite little new ES feature is how Object.fromEntries can make this clearer:

const singularizations = Object.fromEntries(Object.entries(pluralizations).map(([k,v]) => [v,k]))

@dgeb
Copy link
Member

dgeb commented Jun 22, 2021

Ah yes, that is sooo much clearer!

@ef4 ef4 closed this as completed Jun 22, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants