Apollo-feature-requests: Add a way to strip __typename field from query and mutation results

Created on 27 Jul 2018  Â·  59Comments  Â·  Source: apollographql/apollo-feature-requests

Migrated from: apollographql/apollo-client#1564

project-apollo-client

Most helpful comment

@freshcoat no one talks about removing __typename from the cache. We just would like to have an option to not fetch it from the cache or at least remove it automatically when sending to mutation. That's it

All 59 comments

Isn't this whats filter from graphql-anywhere is about?

https://www.apollographql.com/docs/react/advanced/fragments.html#filtering-with-fragments

@smeijer not really. It's the same as addTypename but per query or mutation instead of a global one.

This is really killing us right now and it'd be a huge help if this functionality was built into apollo

For anyone using Angular, I am currently using this middleware which strips the '__typename' off all requests. It has come in very handy for me. perhaps someone else would benefit.

`
import { ApolloLink } from 'apollo-link';

   export class AppModule {

  constructor(private httpLink: HttpLink, private apollo: Apollo) {

    const http = httpLink.create({ 
      uri: environment.graphqlLink,
      withCredentials: true
    });

    const typenameMiddleware = new ApolloLink((operation, forward) => {
      if (operation.variables) {
        operation.variables = JSON.parse(JSON.stringify(operation.variables), this.omitTypename)
      }
      return forward(operation)
    });

    const myAppLink = ApolloLink.from([typenameMiddleware, http]);
    apollo.create({
      link: myAppLink, 
      cache: new InMemoryCache()
    });
    }
    private omitTypename(key, value) {
      return key === '__typename' ? undefined : value
    }
  }

`

For anyone using Angular, I am currently using this middleware which strips the '__typename' off all requests. It has come in very handy for me. perhaps someone else would benefit.

`
import { ApolloLink } from 'apollo-link';

   export class AppModule {

  constructor(private httpLink: HttpLink, private apollo: Apollo) {

    const http = httpLink.create({ 
      uri: environment.graphqlLink,
      withCredentials: true
    });

    const typenameMiddleware = new ApolloLink((operation, forward) => {
      if (operation.variables) {
        operation.variables = JSON.parse(JSON.stringify(operation.variables), this.omitTypename)
      }
      return forward(operation)
    });

    const myAppLink = ApolloLink.from([typenameMiddleware, http]);
    apollo.create({
      link: myAppLink, 
      cache: new InMemoryCache()
    });
    }
    private omitTypename(key, value) {
      return key === '__typename' ? undefined : value
    }
  }

`

This solution will be failed if you upload file. The file will be converted to object ({}) after use JSON.parse.

Working with Apollo cache in JS outside of React I ran into this issue. For some reason none of the suggested fixes worked for me. Maybe that's because I have a deeply nested data structure and to accommodate that I use Fragments.

In JS, stripping __typename turned out to be fairly easy. After some trial and error, I came up with this:

  // Deep copy of uiParent
  const uiParentCleaned = JSON.parse(JSON.stringify(uiParent))

  // Strip __typename from uiParent and item list
  delete uiParentCleaned.__typename
  uiParentCleaned.items.map((item) => (
    // eslint-disable-next-line no-param-reassign
    delete item.__typename
  ))

Any updates here?

Please add this functionality. Super frustrating this is an issue.

For all the people using the JSON.parse approach like https://github.com/apollographql/apollo-client/issues/1564#issuecomment-357492659 or https://github.com/apollographql/apollo-feature-requests/issues/6#issuecomment-428560796 a word of caution: if there is anything that is not a JS primitive like a File or a Blob , that will be removed (as in cast to the closest stringifiable value like {}) and never be sent as part of the graphQl call and File Upload for instance won't work anymore, this was the cause of a bad afternoon for me so hopefully you won't find yourself in this situation 😄

We ran into the exact same issue today @matteo-hertel. Out of curiosity, what did you end up doing to get around it? We have a deeply nested structure and it feels very icky to drill into it to avoid stringifying the File.

I've sticked togheter a function to deeply remove the typename and making sure I'm not touching the File object

function stripTypenames(obj: any, propToDelete: string) {
  for (const property in obj) {
    if (typeof obj[property] === 'object' && !(obj[property] instanceof File)) {
      delete obj.property;
      const newData = stripTypenames(obj[property], propToDelete);
      obj[property] = newData;
    } else {
      if (property === propToDelete) {
        delete obj[property];
      }
    }
  }
  return obj;
}

and added it as middleware

const removeTypenameMiddleware = new ApolloLink(
  (operation: OperationInterface, forward: ()=>voil | null) => {
    if (operation.variables) {
      operation.variables = stripTypenames(operation.variables, '__typename');
      return forward ? forward(operation) : null;
    }
  },
);

it worked quite well but I'm not 100% happy with it

It's frustrating that this feature is still not implemented. It isn't big of a deal for flat structures but if you have nested objects then it's really annoying to have to remove all the __typenames. There should be an option to remove __typenames when querying. From what I understand __typename is only necessary to properly identify types in the cache

@lukejagodzinski When working with a union you need something like the __typename to act as a discriminant so you know what nested object you're working with. So that's not a feature you always want.

@fbartho So that's not a feature you always want.

No argument there. But it would sure be nice to have a simple documented way to eliminate __typename when it gets in the way. Or even better, to automatically do the necessary parameter modifications so it becomes a non-issue for all devs.

As I recall, I spent a few days wrestling with this issue before I got it settled.

Removing the __typename shouldn’t be the way to go as it’s important to keep the cache in sync (cache uses the typenames). I am using the ‘omit’ function of lodash to remove the typename from the object before doing an mutation, this works great for us.

@freshcoat no one talks about removing __typename from the cache. We just would like to have an option to not fetch it from the cache or at least remove it automatically when sending to mutation. That's it

You are able to change what the cache uses for an ID also, instead of '__typename'.

Why does the mutation care if there are additional fields in variables at all? Can't Apollo Client just use the fields it needs and filter out the rest?

In most cases @elie222 the '__typename' is not something we include in our inputs, therefore when the mutation is sent to the server along with a '__typename' it doesn't match the shape of our input which is causing issues.

When pulling down data from the server it is already on the objects & become redundant to continually remove it.

Personally, in my projects, I create an ApolloLink which strips '__typename' out along with undefined values as shown above.

@RyannGalea I understand the issue. I'm saying that in general, whether the extra field is called __typename, extraType or anything else, it should be stripped from the request. I ran into this issue myself that there was an extra id field for a mutation I was making. I had to strip this field myself.

The middleware solution isn't working for me. Why there is no __typename field within the operations.variables.

A nice solution would be to make the __typename field invisible to JSON serialization. A classic way to add metadata to objects is to use a symbol as the property key, which makes the property automatically non-enumerable.

const obj = { hello: "world" }
const typenameSymbol = Symbol("apolloTypename")
Object.defineProperty(obj, typenameSymbol, { value: "Test" })

console.log(obj)
// --> { hello: 'world' }

console.log(JSON.stringify(obj))
// --> {"hello":"world"}

console.log(obj[typenameSymbol])
// --> Test

+1 for symbols sounds great :+1:

In case someone needs stripping only for firing of mutations and not on each query/subscription (in TypeScript):

import { GraphQLError, OperationDefinitionNode } from "graphql";
import { getMainDefinition } from "apollo-utilities";

const cleanTypenameLink = new ApolloLink((operation, forward) => {
  const omitTypename = (key: any, value: any) =>
    key === "__typename" ? undefined : value;

  const def = getMainDefinition(operation.query); 
  if (def && (<OperationDefinitionNode>def).operation === "mutation") {
    operation.variables = parse(stringify(operation.variables), omitTypename);
  }
  return forward ? forward(operation) : null;
});

Why is this still not implemented? We need QoL for the development process please

I run into this issue too. Unluckily the v2.6 client set the object to strict mode. This means it is even not possible to delete __typename "in-place" . A copy of the origin object is required to delete __typename.

Dear @hwillson and @benjamn,

I'm mentioning you here as I think you are currently the most active maintainers of the Apollo Client.
I think the livelihood of an open-source project is not only determined by the number of its contributors or its Github stars but it is also determined by how its maintainers participate in the community discussions.

I couldn't find any maintainers response in this issue and some other issues with high number of community reactions.

Leaving a _PRs welcome_ comment and guiding the community on how to contribute this feature, or even rejecting it altogether because of some architectural decisions, ensures us that this project is alive and community voice is being heard.

Thank you for this awesome tool.

The solution provided by @dafrie does work, but can cause problems when you have non serializable objects in your payload. In my case I'm uploading files in my mutation. These File Objects get stripped out to only include the pathname.

I have adapted it to use omit-deep-lodash to prevent this.

import omitDeep from 'omit-deep-lodash'

const cleanTypenameLink = new ApolloLink((operation, forward) => {
  const keysToOmit = ['__typename'] // more keys like timestamps could be included here

  const def = getMainDefinition(operation.query)
  if (def && def.operation === 'mutation') {
    operation.variables = omitDeep(operation.variables, keysToOmit)
  }
  return forward ? forward(operation) : null
})

@nik-lampe Thanks for the workaround, not ideal that this is necessary, but seems to be working nicely!

Our team is migrating to Apollo, this is a show-stopper.

Not because of the issue, simply because of the lack of any meaningful response on such a large issue in such a long period. It has dropped our confidence level that Apollo is actually being maintained.

For anyone using Angular, I am currently using this middleware which strips the '__typename' off all requests. It has come in very handy for me. perhaps someone else would benefit.
`
import { ApolloLink } from 'apollo-link';

   export class AppModule {

  constructor(private httpLink: HttpLink, private apollo: Apollo) {

    const http = httpLink.create({ 
      uri: environment.graphqlLink,
      withCredentials: true
    });

    const typenameMiddleware = new ApolloLink((operation, forward) => {
      if (operation.variables) {
        operation.variables = JSON.parse(JSON.stringify(operation.variables), this.omitTypename)
      }
      return forward(operation)
    });

    const myAppLink = ApolloLink.from([typenameMiddleware, http]);
    apollo.create({
      link: myAppLink, 
      cache: new InMemoryCache()
    });
    }
    private omitTypename(key, value) {
      return key === '__typename' ? undefined : value
    }
  }

`

This solution will be failed if you upload file. The file will be converted to object ({}) after use JSON.parse.

This is what I do to the avoid issues with my file upload integration.

  const typenameLink = new ApolloLink((operation, forward) => {
    if (operation.variables) {
      let { fileInput, ...others } = operation.variables
      others = omitDeep(JSON.parse(JSON.stringify(others)), ['__typename'])
      const { input } = others
      operation.variables = {
        input: RemoveNullValues(input),
        fileInput,
        ...others
      }
    }
    return forward(operation)
  })

I understand the struggles here and pragmatism, but the approach that causes these problems - sending output types back as inputs - seems to go against the GraphQL specification as well as any hope of command query responsibility segregation (CQRS) and truly type-safe operations.

"Input Object types can only be used as input types. Object, Interface, and Union types can only be used as output types."

https://spec.graphql.org/June2018/#sec-Input-and-Output-Types

Also some good reading: https://stackoverflow.com/a/55881719

This has been open for _years_, guys. come on.

In their defence, this is a problem faced by all GQL client implementations. see urql etc.

That's fair. I was also in a shit mood yesterday. I ended up using a variation of the middleware link and sorted it out. My snide comment aside, I do think that this pretty poor developer ergonomics for an exceedingly common problem.

But hey, open source, etc etc and I'm quite happy to have and use the library and appreciate the work the maintainers have done on it.

haha yes... well I do agree there are allot of issues with Apollo docs that have cost us huuuuge amounts of time. But than again we dont really contribute either.

Would be great to have this, doing it manually until then :/

I for one would like to say F __typename implementation. @Mottoweb @everybodyelse what is the optimal way of removing __typename manually, at the moment?

@AndyDeNike Mapping through and delete obj.__typename on each.

My case may be different though, since I need that in only one place in the code.

Just put this on the exceedingly long list of issues and tribal knowledge required in modern web development. GitHub issues are as good as docs!

Here's what I use, in case it's helpful for anyone else (uses lodash). The Typescript annotations aren't perfect (it only recognizes the omitted __typename one level deep) but I've found it good enough for my use cases so far:

import { isPlainObject, isArray } from 'lodash';

export default function cloneWithoutTypename<T>(obj: T): Omit<T, '__typename'> {
  if (!isPlainObject(obj)) {
    return obj;
  }
  let result: any = {};
  for (let key in obj) {
    if (key === '__typename') {
      continue;
    }
    let value = (obj as any)[key];
    if (isPlainObject(value)) {
      result[key] = cloneWithoutTypename(value);
    } else if (isArray(value)) {
      result[key] = value.map(cloneWithoutTypename);
    } else {
      result[key] = value;
    }
  }
  return result;
}

I had this code for months in my project but I ran into an error when doing the Upload of a File object (with ReactNativeFile of apollo-upload-client). When using the omitDeep or the many of the Apollolinks here provided the ReactNativeFile object is transformed to a regular object. Causing your backend to probably not accept the object for upload.

Just a comment on this issue as it can have this as a result and hopefully prevent some lost hours of my colleague-developers.

@stvdrsch

It's what I use to handle file imports:

// Used to remove typename property from objects
const isFile = value =>
  (typeof File !== 'undefined' && value instanceof File) || (typeof Blob !== 'undefined' && value instanceof Blob);

// From https://gist.github.com/Billy-/d94b65998501736bfe6521eadc1ab538
const omitDeep = (value, key) => {
  if (Array.isArray(value)) {
    return value.map(i => omitDeep(i, key));
  } else if (typeof value === 'object' && value !== null && !isFile(value)) {
    return Object.keys(value).reduce((newObject, k) => {
      if (k === key) return newObject;
      return Object.assign({ [k]: omitDeep(value[k], key) }, newObject);
    }, {});
  }
  return value;
};

const omitTypenameLink = new ApolloLink((operation, forward) => {
  if (operation.variables) {
    operation.variables = omitDeep(operation.variables, '__typename');
  }
  return forward(operation);
});

Hello, landed here due to functionality testing of Prisma 2 and type-graphql, for the purpose of spreading awareness about this behaviour between the projects and developers working with apollo-server+client, here is a link to the discussion: https://github.com/MichalLytek/type-graphql/issues/476#issuecomment-663639646

Here's a workaround, using clone-deep and omit-deep NPM packages:

import cloneDeep from 'clone-deep';
import omitDeep from 'omit-deep';

...

const someObject = data.someObject; // data comes from Query component or useQuery hoook

await someMutation({
  variables: {
    ...
    someObject: omitDeep(cloneDeep(someObject), '__typename'),
    ...
  },
});

Since this issue the first result on Google:

There is actually a good answer here: https://stackoverflow.com/questions/47211778/cleaning-unwanted-fields-from-graphql-responses/51380645#51380645

Quick workaround is:

apollo.create({
  link: http,
  cache: new InMemoryCache({
    addTypename: false
  })
});

But it seems to mess up the cache so I created a link instead and so far so good:

const cleanTypeName = new ApolloLink((operation, forward) => {
  if (operation.variables) {
    const omitTypename = (key: string, value: any) =>
      key === "__typename" ? undefined : value;
    operation.variables = JSON.parse(
      JSON.stringify(operation.variables),
      omitTypename
    );
  }
  return forward(operation).map((data) => {
    return data;
  });
});

export default new ApolloClient({
  cache: new InMemoryCache(),
  link: ApolloLink.from([cleanTypeName, authLink, errorLink as any, httpLink]),
});

FWIW, I used fetchPolicy: 'no-cache' on the query for data to be mutated. This at least makes the object mutable so you can delete the __typename entries without deep cloning.

Not sure if anyone suggested this but it seemed to work for me

apollo.create({
  link: http,
  cache: new InMemoryCache({
    addTypename: false
  })
});
const httpLink = createHttpLink({
 uri: process.env.REACT_APP_GRAPH_ENDPOINT,
  credentials: 'include'
});

const middlewareLink = new ApolloLink((operation, forward) => {
  operation.setContext({
    headers: {
      accessToken: localStorage.getItem('x-access-token') || null,
      refreshToken: localStorage.getItem('x-refresh-token') || null
    }
  });

  return forward(operation);
});

const link = middlewareLink.concat(httpLink);

const client = new ApolloClient({
  cache: new InMemoryCache(({
    addTypename: false
  })),
  link
});

My whole example looks like the above and the ___typename_ is no longer present for me
Not my solution but pulled from here

Not sure if anyone suggested this but it seemed to work for me

apollo.create({
  link: http,
  cache: new InMemoryCache({
    addTypename: false
  })
});
const httpLink = createHttpLink({
 uri: process.env.REACT_APP_GRAPH_ENDPOINT,
  credentials: 'include'
});

const middlewareLink = new ApolloLink((operation, forward) => {
  operation.setContext({
    headers: {
      accessToken: localStorage.getItem('x-access-token') || null,
      refreshToken: localStorage.getItem('x-refresh-token') || null
    }
  });

  return forward(operation);
});

const link = middlewareLink.concat(httpLink);

const client = new ApolloClient({
  cache: new InMemoryCache(({
    addTypename: false
  })),
  link
});

My whole example looks like the above and the ___typename_ is no longer present for me
Not my solution but pulled from [here](https://stackoverflow.com/questions/47211778/cleaning-unwanted-fields-from-graphql-responses/51380645#51380645

I don't recommend that because you might break the cache.

Another thing that we break removing __typename is Unions since there is no way of knowing the type of the Union unless you check __typename.

Example

Notifications data property is a message union type which can be ProjectNotifications or Machine Notifications the both of them have separated notifications for every union. In the end we have a generic notifications type which can scale without repetition
If we remove __typename this graphql feature will not be available.

Screenshot from 2020-10-20 20-43-26

Screenshot from 2020-10-20 20-42-27

Hi. I am new in graphQL but as I see we need __typename for union types.
By the way, I am wondering why I can't add __typename: String! to the input type. then problem should be solved.
When I add __typename: String! to the input type, I get an error:
Name "__typename" must not begin with "__", which is reserved by GraphQL introspection.

const client = new ApolloClient({
link: new HttpLink({uri: MY_URL}),
cache: new InMemoryCache({
addTypename: false
})
});

Here's a basic function I use to strip typename from mutations:

  function stripTypename<T>(input: T) {
    const newish = { ...input };

    for (const prop in newish) {
      if (prop === '__typename') delete newish[prop];
      else if (newish[prop] === null) {
      } else if (Array.isArray(newish[prop])) {
        for (const next in newish[prop]) {
          newish[prop][next] = stripTypename(newish[prop][next]);
        }
      } else if (typeof newish[prop] === 'object') {
        newish[prop] = stripTypename(newish[prop]);
      }
    }

    return newish;
  }

This doesn't effect files or cache. Might be a bit heavy for larger datasets.

I found an approach to this that may work for some folks. I started using Yup to define entity schemas for form validation. I'm casting Apollo cache objects to the Yup schema. During casting, there's an option to strip out unknown properties like __typename. I then edit and post the Yup-created object instead of the Apollo cache object. Works great.

I found an approach to this that may work for some folks. I started using Yup to define entity schemas for form validation. I'm casting Apollo cache objects to the Yup schema. During casting, there's an option to strip out unknown properties like __typename. I then edit and post the Yup-created object instead of the Apollo cache object. Works great.

@anthonysapien got an example to share?

Ideally for me, it'd be to strip fields that are not required in the input object of a mutation. __typename is one problem, but others are adding fields that are extra but come back from a query.

@mmahalwy Here's a way to use a block list with mine:

  function stripTypename<T>(input: T, blocklist: string[]) {
    const newish = { ...input };

    for (const prop in newish) {
      if (blocklist.contains(prop)) delete newish[prop];
      else if (newish[prop] === null) {
      } else if (Array.isArray(newish[prop])) {
        for (const next in newish[prop]) {
          newish[prop][next] = stripTypename(newish[prop][next]);
        }
      } else if (typeof newish[prop] === 'object') {
        newish[prop] = stripTypename(newish[prop]);
      }
    }

    return newish;
  }

You'd have to manually define the blocklist, but you could also specify the inverse for an allow list if that's easier.

@mmahalwy here's a short example.

First, create a schema that can be used to for standalone validation or with a library like Formik.
Then cast the Apollo cache object, and remove unknown properties like __typename.
The example doesn't demonstrate nested entities, but that's works well too.

const schema = yup.object().shape({
  id: yup.string(),
  name: yup.string().default('').required('Name is required'),
});

const initialValues = schema.cast(user, { stripUnknown: true });

@anthonysapien that would work but more overkill on the frontend part. Ideally, it's more automatic. Ideally ideally, apollo detects the fields needed to send (as defined from the API in the input object) and only sends those.

@mmahalwy I agree. It's overkill to solve this problem. Just wanted to share in case others also need validation and want to get past this quickly.

Was this page helpful?
0 / 5 - 0 ratings