简体   繁体   English

需要帮助转换 GraphQL 结果

[英]Need help transforming GraphQL results

A returned data object looks like the following:返回的数据 object 如下所示:

{
  data: {
    posts: {
      edges: [
        {
          post: {
            id: "1",
            title: "Foo"
          }
        },
        {
          post: {
            id: "2",
            title: "Bar"
          }
        }
      ]
    }
  }
}

This is based on the following query:这基于以下查询:

    query MyQuery {
      posts {
        edges {
          post: node {
            id
            title
          }
        }
      }
    }

This works and I can use it, but I'm having to create nested interfaces, unfortunately.这行得通,我可以使用它,但不幸的是,我不得不创建嵌套接口。

Question: Can I either simplify the returned results OR transform them with JavaScript map() ?问题:我可以简化返回的结果或使用 JavaScript map()转换它们吗?

Ideally, I'd like for the GQL response (or resulting object) to be like:理想情况下,我希望 GQL 响应(或结果对象)如下所示:

{
  data: {
    posts: [
      {
        id: "1",
        title: "Foo"
      },
      {
        id: "2",
        title: "Bar"
      }
    ]
  }
}

Note: I do not have the ability to update the server-side GraphQL schema.注意:我无法更新服务器端 GraphQL 架构。 The solution must be client/consumer side.解决方案必须是客户端/消费者端。

Thanks!谢谢!

EDIT编辑

Adding my Angular/TS code that calls and processes the GraphQL...添加调用和处理 GraphQL 的 Angular/TS 代码...

post.service.ts post.service.ts

import { Injectable } from '@angular/core';
import { Apollo, gql } from 'apollo-angular';
import { map, Observable } from 'rxjs';
import { GraphQLResponse } from 'src/app/core/types/graphQLResponse';
import { Post } from '../models/post';

export interface PostResponse {
  edges: Post[]
  pageInfo: {
    startCursor: string
    hasPreviousPage: boolean
    hasNextPage: boolean
    endCursor: string
  }
}

export const getPostsQuery = gql`
  query getPostsQuery {
    posts {
      edges {
        post: node {
          id
          title
          date
          uri
          categories {
            edges {
              category: node {
                id
                name
                uri
              }
            }
          }
        }
        cursor
      }
      pageInfo {
        startCursor
        hasPreviousPage
        hasNextPage
        endCursor
      }
    }
  }
`;

@Injectable({
  providedIn: 'root'
})
export class PostService {

  constructor(private apollo: Apollo) { }

  public getPosts(): Observable<PostResponse> {
   
    return this.apollo.query<GraphQLResponse<'posts', PostResponse>>({
      query: getPostsQuery
    }).pipe(map(resp => resp.data.posts));
  }
}

model/post.ts模型/post.ts

interface CategoryNode {
    id: string;
    name: string;
    uri: string;
}

interface Category {
    category: CategoryNode;
}

interface CategoryEdges{
    edges: Category[];
}

interface PostNode {
    id: string;
    title: string;
    date: string;
    uri: string;
    categories: CategoryEdges;
}

export interface Post {
    article: PostNode;
    cursor: string;
}

As you can see, way too many nested interfaces.如您所见,嵌套接口太多了。

Actual sample response (used for unit testing)实际样本响应(用于单元测试)

      {
        data: {
          posts: {
            edges : [
              {
                post: {
                  id: "cG9zdDoxMjc=",
                  title: "Lorem Ipsum",
                  date: "2022-01-06T22:00:53",
                  uri: "\/2022\/01\/06\/lorem-ipsum\/",
                  categories: {
                    edges: [
                      {
                        category: {
                          id: "dGVybToy",
                          name: "General",
                          uri: "\/category\/general\/"
                        }
                      }
                    ]
                  }
                },
                cursor: "YXJyYXljb25uZWN0aW9uOjEyNw=="
              },
              {
                post: {
                  id: "cG9zdDoxMjc=",
                  title: "Lorem Ipsum",
                  date: "2022-01-06T22:00:53",
                  uri: "\/2022\/01\/06\/lorem-ipsum\/",
                  categories: {
                    edges: [
                      {
                        category: {
                          id: "dGVybToy",
                          name: "General",
                          uri: "\/category\/general\/"
                        }
                      },
                      {
                        category: {
                          id: "dGVybToy",
                          name: "General",
                          uri: "\/category\/general\/"
                        }
                      }
                    ]
                  }
                },
                cursor: "YXJyYXljb25uZWN0aW9uOjEyNw=="
              },
            ],
            pageInfo: {
              startCursor: "YXJyYXljb25uZWN0aW9uOjEyNw==",
              hasPreviousPage: false,
              hasNextPage: false,
              endCursor: "YXJyYXljb25uZWN0aW9uOjEyNw=="
            }       
          }
        }
      }; 

Without being able to introspect the GQL schema, it's difficult to advise you on how to modify your query to get the shape that you want (if it's possible), but without modifying your query, you can transform the response value into the shape that you want like this:如果无法自省 GQL 架构,很难就如何修改查询以获得所需的形状提供建议(如果可能的话),但在不修改查询的情况下,您可以将响应值转换为您想要的形状想要这样:

TS Playground TS游乐场

interface PostNode {
  id: string;
  title: string;
}

interface PostEdge { post: PostNode; }

type GQLResponse<T> = { data: T; };

type PostsResponse = GQLResponse<{
  posts: {
    edges: PostEdge[];
  };
}>;

type TransformedPostsResponse = {
  data: {
    posts: PostNode[];
  };
};

function transformPostsResponse (res: PostsResponse): TransformedPostsResponse {
  const result: TransformedPostsResponse = {data: {posts: []}};
  for (const edge of res.data.posts.edges) result.data.posts.push(edge.post);
  return result;
}

const postsResponse: PostsResponse = {
  data: {
    posts: {
      edges: [
        {
          post: {
            id: "1",
            title: "Foo"
          }
        },
        {
          post: {
            id: "2",
            title: "Bar"
          }
        }
      ]
    }
  }
};

const result = transformPostsResponse(postsResponse);
console.log(result);

Demo (compiled JS from the TS Playground):演示(从 TS Playground 编译的 JS):

 "use strict"; function transformPostsResponse(res) { const result = { data: { posts: [] } }; for (const edge of res.data.posts.edges) result.data.posts.push(edge.post); return result; } const postsResponse = { data: { posts: { edges: [ { post: { id: "1", title: "Foo" } }, { post: { id: "2", title: "Bar" } } ] } } }; const result = transformPostsResponse(postsResponse); console.log(result);

I ended up using nested map() 'ing to transform the GraphQL response to a "cleaner" object.我最终使用嵌套的map()将 GraphQL 响应转换为“更清洁”的 object。

Below is my final code, if anyone has the same question/issue.如果有人有同样的问题/问题,下面是我的最终代码。

NOTE: In the code below I'm using "articles" instead of "posts," but it's the same concept.注意:在下面的代码中,我使用的是“文章”而不是“帖子”,但这是同一个概念。

models/article-gql.ts模型/文章-gql.ts

interface GqlCategoryNode {
    category: {
        id: string;
        name: string;
        uri: string;
    };
}

interface GqlArticleNode {
    article: {
        id: string;
        title: string;
        date: string;
        uri: string;
        categories: {
            edges: GqlCategoryNode[]
        };
    };
    cursor: string;
}

export interface GqlArticleResponse {
    edges: GqlArticleNode[]
    pageInfo: {
        startCursor: string
        hasPreviousPage: boolean
        hasNextPage: boolean
        endCursor: string
    }
}

models/article.ts模型/article.ts

interface Category {
    id: string;
    name: string;
    uri: string;
}

export interface Article {
    id: string;
    title: string;
    date: string;
    uri: string;
    categories: Category[];
    cursor: string;
}

export interface PageInfo {
    startCursor: string;
    hasPreviousPage: boolean;
    hasNextPage: boolean;
    endCursor: string;
}

article.service.ts文章.service.ts

import { Injectable } from '@angular/core';
import { Apollo, gql } from 'apollo-angular';
import { map, Observable } from 'rxjs';
import { GraphQLResponse } from 'src/app/core/types/graphQLResponse';
import { Article, PageInfo } from '../models/article';
import { GqlArticleResponse } from '../models/article-gql';

export const getArticlesQuery = gql`
  query getArticlesQuery {
    articles: posts {
      edges {
        article: node {
          id
          title
          date
          uri
          categories {
            edges {
              category: node {
                id
                name
                uri
              }
            }
          }
        }
        cursor
      }
      pageInfo {
        startCursor
        hasPreviousPage
        hasNextPage
        endCursor
      }
    }
  }
`;

@Injectable({
  providedIn: 'root'
})
export class ArticleService {

  constructor(private apollo: Apollo) { }

  public getArticles(): Observable<[PageInfo, Article[]]> {
    return this.apollo.query<GraphQLResponse<'articles', GqlArticleResponse>>({
      query: getArticlesQuery
    }).pipe(map(resp => {
      return [
        resp.data.articles.pageInfo as PageInfo,
        resp.data.articles.edges.map((articleNode) => {
        return {
          id: articleNode.article.id,
          title: articleNode.article.title,
          date: articleNode.article.date,
          uri: articleNode.article.uri,
          cursor: articleNode.cursor,
          categories: articleNode.article.categories.edges.map((categoryNode) => {
            return {
              id: categoryNode.category.id,
              name: categoryNode.category.name,
              uri: categoryNode.category.uri
            }
          })
        }
      })]
    })) as Observable<[PageInfo, Article[]]>;
  }

}

article.service.spec.ts article.service.spec.ts

Below you will notice that I'm transforming the server response within the service and testing the response from the service to ensure it was transformed as expected.下面您会注意到我正在转换服务中的服务器响应并测试来自服务的响应以确保它按预期进行转换。

import { TestBed } from '@angular/core/testing';
import { Apollo } from 'apollo-angular';
import { ApolloTestingController, ApolloTestingModule } from 'apollo-angular/testing';
import { Article, PageInfo } from '../models/article';
import { GqlArticleResponse } from '../models/article-gql';
import { ArticleService, getArticlesQuery } from './article.service';


describe('ArticleService', () => {
  let service: ArticleService;
  let controller: ApolloTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        ApolloTestingModule,
      ],
      providers: [
        ArticleService
      ]
    });
    service = TestBed.inject(ArticleService);
    controller = TestBed.inject(ApolloTestingController);
  });

  afterEach(async () => {
    const apolloClient = TestBed.inject(Apollo).client;
    await apolloClient.clearStore();
  })

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  it('should return a list of articles', (done) => {
    const mockArticlesServerResponse: GqlArticleResponse = {
      edges: [
        {
          article: {
            id: "cG9zdDoxMjc=",
            title: "Lorem Ipsum",
            date: "2022-01-06T22:00:53",
            uri: "\/2022\/01\/06\/lorem-ipsum\/",
            categories: {
              edges: [
                {
                  category: {
                    id: "dGVybToy",
                    name: "General",
                    uri: "\/category\/general\/"
                  }
                }
              ]
            }
          },
          cursor: "YXJyYXljb25uZWN0aW9uOjEyNw=="
        },
        {
          article: {
            id: "cG9zdDoxMjc=",
            title: "Lorem Ipsum",
            date: "2022-01-06T22:00:53",
            uri: "\/2022\/01\/06\/lorem-ipsum\/",
            categories: {
              edges: [
                {
                  category: {
                    id: "dGVybToy",
                    name: "General",
                    uri: "\/category\/general\/"
                  }
                },
                {
                  category: {
                    id: "dGVybToy",
                    name: "Something",
                    uri: "\/category\/general\/"
                  }
                }
              ]
            }
          },
          cursor: "YXJyYXljb25uZWN0aW9uOjEyNw=="
        }
      ],
      pageInfo: {
        startCursor: "YXJyYXljb25uZWN0aW9uOjEyNw==",
        hasPreviousPage: false,
        hasNextPage: false,
        endCursor: "YXJyYXljb25uZWN0aW9uOjEyNw=="
      }
    };

    const mockArticlesServiceResponse: [PageInfo, Article[]] = [
      {
        startCursor: "YXJyYXljb25uZWN0aW9uOjEyNw==",
        hasPreviousPage: false,
        hasNextPage: false,
        endCursor: "YXJyYXljb25uZWN0aW9uOjEyNw=="
      },
      [
        {
          id: "cG9zdDoxMjc=",
          title: "Lorem Ipsum",
          date: "2022-01-06T22:00:53",
          uri: "\/2022\/01\/06\/lorem-ipsum\/",
          categories: [
            {
              id: "dGVybToy",
              name: "General",
              uri: "\/category\/general\/"
            }
          ],
          cursor: "YXJyYXljb25uZWN0aW9uOjEyNw=="
        },
        {
          id: "cG9zdDoxMjc=",
          title: "Lorem Ipsum",
          date: "2022-01-06T22:00:53",
          uri: "\/2022\/01\/06\/lorem-ipsum\/",
          categories: [
            {
              id: "dGVybToy",
              name: "General",
              uri: "\/category\/general\/"
            },
            {
              id: "dGVybToy",
              name: "Something",
              uri: "\/category\/general\/"
            }
          ],
          cursor: "YXJyYXljb25uZWN0aW9uOjEyNw=="
        }
      ]
    ];

    service.getArticles().subscribe(resp => {
      expect(resp).toEqual(mockArticlesServiceResponse);
      done();
    });

    const req = controller.expectOne(getArticlesQuery);
    expect(req.operation.operationName).toBe('getArticlesQuery');
    req.flush({ data: { articles: mockArticlesServerResponse } });
    controller.verify();

  });
});

Thanks everyone for your input and assistance!感谢大家的投入和帮助!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 需要帮助以延迟angularjs中的搜索结果 - Need help to delay of search results in angularjs 需要Extjs函数返回未定义结果的帮助 - Need help with Extjs function returning undefined results 需要帮助以表格形式显示代码结果 - Need help showing code results in a table 需要帮助根据测验结果改变背景颜色? - Need help changing background colour based on results obtained from quiz? Rails:需要帮助来构建基本的AJAX搜索表单并显示结果 - Rails: Need help building a basic AJAX search form and displaying results 如何完成对从GraphQL查询返回的JSON的转换? - How can I finish transforming this JSON returned from a GraphQL query? 需要帮助将数组中的每个数字与其后面的数字相乘,然后显示这些结果 - Need help multiplying each number in the array by the number after it, and getting those results to show up 使用 AXIOS 进行电影搜索后,OMDb API 需要帮助获取 /results 页面 - OMDb API need help getting /results page after movie search using AXIOS 需要帮助使用jquery搜索XML文件并将结果存储在变量中以供以后使用 - Need help searching through an XML file using jquery and storing results in variable for later use 需要帮助确定如何制作脚本以将数字转换为变量并对数组进行排序并显示结果 - Need help figuring how to make script to convert number to variable and sorting the array and displaying the results
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM