[英]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!谢谢!
Adding my Angular/TS code that calls and processes the GraphQL...添加调用和处理 GraphQL 的 Angular/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));
}
}
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.如您所见,嵌套接口太多了。
{
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 架构,很难就如何修改查询以获得所需的形状提供建议(如果可能的话),但在不修改查询的情况下,您可以将响应值转换为您想要的形状想要这样:
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.注意:在下面的代码中,我使用的是“文章”而不是“帖子”,但这是同一个概念。
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
}
}
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;
}
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[]]>;
}
}
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.