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

Add a way to strip __typename field from query and mutation results #6

Closed
vladshcherbin opened this issue Jul 27, 2018 · 91 comments
Closed
Assignees
Labels
🔗 apollo-link Feature requests related to Apollo Link behavior project-apollo-client (legacy) LEGACY TAG DO NOT USE

Comments

@vladshcherbin
Copy link

Migrated from: apollographql/apollo-client#1564

@hwillson hwillson added the project-apollo-client (legacy) LEGACY TAG DO NOT USE label Jul 27, 2018
@vladshcherbin vladshcherbin changed the title __typename field prevents updates Add a way to strip __typename field from query and mutation results Jul 27, 2018
@smeijer
Copy link

smeijer commented Aug 31, 2018

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

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

@vladshcherbin
Copy link
Author

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

@joshdotblack
Copy link

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

@RyannGalea
Copy link

RyannGalea commented Oct 10, 2018

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
    }
  }

`

@nvuhung
Copy link

nvuhung commented Oct 27, 2018

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.

@gihrig
Copy link

gihrig commented Nov 4, 2018

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
  ))

@RealSilo
Copy link

RealSilo commented Feb 8, 2019

Any updates here?

@djmgh
Copy link

djmgh commented Feb 9, 2019

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

@matteo-hertel
Copy link

For all the people using the JSON.parse approach like apollographql/apollo-client#1564 (comment) or #6 (comment) 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 😄

@casto101
Copy link

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.

@matteo-hertel
Copy link

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

@lukejagodzinski
Copy link

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

@fbartho
Copy link

fbartho commented Apr 8, 2019

@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.

@gihrig
Copy link

gihrig commented Apr 8, 2019

@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.

@freshcoat
Copy link

freshcoat commented Apr 15, 2019

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.

@lukejagodzinski
Copy link

@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

@RyannGalea
Copy link

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

@elie222
Copy link

elie222 commented Jul 9, 2019

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?

@RyannGalea
Copy link

RyannGalea commented Jul 10, 2019

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.

@elie222
Copy link

elie222 commented Jul 10, 2019

@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.

@ackvf
Copy link

ackvf commented Sep 18, 2019

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

@phjardas
Copy link

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

@CanRau
Copy link

CanRau commented Oct 17, 2019

+1 for symbols sounds great 👍

@dafrie
Copy link

dafrie commented Nov 11, 2019

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;
});

@joshuarobs
Copy link

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

@Hale-ma
Copy link

Hale-ma commented Jan 16, 2020

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.

@j-lee8
Copy link

j-lee8 commented Feb 10, 2022

This issue has frustrated me for so long. Even urql has a way to mask typenames easily.

Currently, whenever I do mutations, I omit the __typename from the object and spread the rest of the props and this is what I pass into the mutation. It works fine without issue, but it feels a bit manual.

I haven't added the addTypename prop to the cache because I'm unsure of the consequences and I already find it very flakey as it is.

@lveillard
Copy link

I thought i was doing something wrong doing this manually everytime. I see is old issue. Happy to add my +1 to a symbol solution, or autoremoval on mutation from apolloClient side.

@shivamrsoft
Copy link

Please suggest a solution asap. A lot of developers are facing this issue now.

@andreujuanc
Copy link

To be honest, this should be treated as a BUG. Simply because I queried query{A, B} and somehow I get {A, B, __typename} this is an unwanted field.

@lveillard
Copy link

lveillard commented Mar 18, 2022

To be honest, this should be treated as a BUG. Simply because I queried query{A, B} and somehow I get {A, B, __typename} this is an unwanted field.

Not a bug because is needed even if you're not asking for it. But the fact that we have to manually remove it or apollo does not accept it for mutations seems like a joke.

Anyways, this should be the solution: #6 (comment)

@thebrucecgit
Copy link

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?

Can we get a response on this query? Why can't the Apollo Client just ignore these additional fields instead of throwing errors?

@MichaelrMentele
Copy link

The stupid and unscalable work around is:
const { __typename, ...input } = someFormData

Link seems nice for now, but symbols and the above seem ideal

@PinkaminaDianePie
Copy link

@tregoning
Copy link

In case it helps anybody you can leverage the (not very well known) replacer parameter in JSON.stringify

Will remove deeply nested __typename fields efficiently from the payload

const cleanPayload = JSON.parse(JSON.stringify(payloadWithTypeName, (name, val) => {
  if (name === '__typename') {
      delete val[name];
  } else {
      return val;
  }
}));

@PinkaminaDianePie
Copy link

@Paul-Cl-ark
Copy link

Facing this issue too

@mistermark
Copy link

Same issue here, but I've (temporarily) solved this with the solution from @MirrorBytes: #6 (comment)

Though, I would much prefer to have a native solution to this.

@SpaceMalamute
Copy link

Same issue, when will you fix it?

@michael-ecklund
Copy link

michael-ecklund commented Dec 23, 2022

Not exactly a pizza supreme, but it does the trick.... Thought I'd share. ¯\(ツ)

/**
 * Iterate through an object until all specified properties have been removed.
 *
 * @param theObject
 * @param removeProperties
 * @returns {{}|*}
 */
function unsetProperties(theObject, removeProperties = []) {
  if (theObject === null || typeof theObject !== 'object' || removeProperties.length === 0) {
    return theObject;
  }

  const dataObject = {};

  Object.keys(theObject).map((key) => {
    if (typeof theObject[key] === 'object') {
      dataObject[key] = unsetProperties(theObject[key], removeProperties);
    }

    if (Array.isArray(theObject[key])) {
      dataObject[key] = [];
      theObject[key].map((item) => {
        if (typeof item === 'object') {
          dataObject[key].push(unsetProperties(item, removeProperties));
        } else {
          dataObject[key].push(item);
        }
        return item;
      });
    }

    if (typeof theObject[key] !== 'object' && !Array.isArray(theObject[key]) && !removeProperties.includes(key)) {
      dataObject[key] = theObject[key];
    }

    return key;
  });

  return dataObject;
}

@livgust
Copy link

livgust commented Feb 4, 2023

Adding on to to the pile - this is a total pain.

@omattman
Copy link

+1

@jerelmiller
Copy link
Member

Hey all 👋

This request seems to have had a LOT of attention over the years. Apologies from the maintainers that we've failed to chime in up to this point.

There is a lot of history here so I'd like to see if I can restate the ask here to see if I'm understanding this correctly. From what I gather, the issue here seems to be that because Apollo automatically adds the __typename field to the GraphQL query document for each request (unless disabled), this causes some headaches when you'd like to use those results as input to a mutation or another query as an argument.

const QUERY = gql`
  query Book {
    book {
      id
      title
      description
    }
  }
`

const MUTATION = gql`
  mutation UpdateBook(book: Book!) {
    book {
      id
      title
      description
    }
  }
`

const MyComponent = () => {
  const { data } = useQuery(QUERY);
  const [updateBook] = useMutation(MUTATION);

  // ignore the lack of loading/error handling here
  const book = data.book;

  return (
    <button 
      onClick={() => {
        updateBook({ 
          variables: { 
            book: { ...book, description: 'Updated description' }
          }
        });
      }}
    >
      Update book
    </button>;
  );
}

Used in this way causes problems because GraphQL inputs don't allow for the use of __typename as a field, therefore the mutation fails.

Does this seem like an accurate reflection of the problem?


A bit about the __typename. We STRONGLY recommend you don't strip these out during the request as the __typename is needed for the cache to do its magic. Without the __typename, data normalization and other such features such as type policies cease to provide much value since data types are difficult to detect with it.

I say this to mention that unless there needs to be a VERY compelling reason to build in the ability to easily strip __typename from the response where possible.

That being said, if my understanding of the issue as mentioned above is correct, we could consider adding the ability to strip __typename automatically during mutations and queries if any variables include that field. This however should be fairly easy to implement in user-space with a custom ApolloLink:

const stripTypename = (value) => {
 // ...
}

const stripTypenameLink = new ApolloLink((operation, forward) => {
  const variables = stripTypename(operation.variables);

  return forward({ ...operation, variables })
})

If anyone would be willing to share more details or anything I've missed, that would be much appreciated. Thanks!

@livgust
Copy link

livgust commented Feb 22, 2023

@jerelmiller Yes, that's right. For me, it's happening because I'm getting objects from a query, modifying them, and then sending them back for an update. __typename is still hanging around, so things are failing.

Several people have offered solutions like the pseudocode you pasted above, but there's desire for something official as this seems like a relatively basic feature.

@jerelmiller
Copy link
Member

@livgust awesome, thanks for confirming! This is definitely something I'll chat about with the team to see what makes the most sense. Appreciate it!

@lveillard
Copy link

lveillard commented Mar 9, 2023 via email

@jerelmiller jerelmiller self-assigned this Mar 28, 2023
@kyleboe
Copy link

kyleboe commented Apr 3, 2023

An implementation thought as I've just recently run into this, an added includeTypename boolean configuration setting on QueryOptions (ref: https://www.apollographql.com/docs/react/data/queries#options) might be a small(er), purely additive change to the API.

I believe your assessment of the situation above is 100% accurate where people (myself included) are attempting to pass the results of a query as variables to a mutation. To be able to toggle this setting on an individual query would allow for developers to address this on a case-by-case basis instead of at the client/link level. Just my two cents though. Thanks for keeping after this and let me know if I can assist in any way!

@jerelmiller
Copy link
Member

@kyleboe appreciate the input!

The biggest problem I have with a setting for the query is that it has downstream effects on caching. Your configured type policies and cache normalization rely on __typename, so while its tempting to turn off __typename for a single query, you might introduce other problems if your queries rely on the normalized cache.

My current thinking is that we transparently just strip __typename fields during any mutation operation for you. This allows you to avoid potentially negative side effects on your cache layer, but lets you call mutations freely without the overhead of needing to strip those yourself.

I've got this on the docket as an upcoming item I'd like to take a look at. I'll try and keep this updated as progress is made. Thanks again for the feedback!

@jerelmiller
Copy link
Member

Hey all 👋 I sincerely appreciate the patience on this feature!

I'm excited to announce that we've merged apollographql/apollo-client#10724 and this feature will be a part of our next minor release v3.8.0 🎉 🎉 🎉. We are looking forward to closing out a nearly 5 year old request!

This change will be transparent to you as long as you are using HttpLink, BatchHttpLink or GraphQLWsLink. These links will automatically strip __typename fields from variables before the request is sent to the server and applies to all operation types (Query, Mutation and Subscription). This should make it much easier to use data from a previous query as input to another query, mutation, or subscription, without the need to remove this field yourself.


v3.8.0 is still in alpha at the time of this writing, so if you'd like to try this out, please do so by installing the alpha release:

npm install @apollo/client@alpha

If you have additional feedback or would like to report any bugs related to this feature, please do so by opening an issue in the apollo-client repo for us to track.

Thanks!

@jerelmiller
Copy link
Member

Hey all 👋 quick update

We found an issue with the new approach. We strip __typename from JSON scalar types unnecessarily and believe these types should be left alone. __typename is a valid GraphQL field inside a JSON scalar and there might be valid uses cases to allow this.

If you have any feedback and would like to chime in on the expected behavior here, please add a comment to apollographql/apollo-client#10767. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🔗 apollo-link Feature requests related to Apollo Link behavior project-apollo-client (legacy) LEGACY TAG DO NOT USE
Projects
None yet
Development

No branches or pull requests