You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Contentful CMS数据提取问题求助:嵌套对象处理及资源推荐

规整Contentful嵌套数据的解决方案

我从Contentful CMS获取数据时,返回的是庞大的嵌套对象,必须通过{{post.section4Facts.content[1].content[1].content[0].value}}这类多层索引才能拿到需要的值,希望能规整这种数据结构,同时寻求相关技术资源(Contentful官网没找到对应解答)。以下是我用的Angular组件代码和模板代码:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ServiceService } from '../service.service';
import * as contentful from 'contentful';

@Component({
  selector: 'app-post-details',
  templateUrl: './post-details.component.html',
  styleUrls: ['./post-details.component.scss']
})
export class PostDetailsComponent implements OnInit {
  postId: any;
  post: any;
  tags: any[] = [];
  constructor(private route: ActivatedRoute, private apiService: ServiceService) { }

  ngOnInit(): void {
    this.route.params.subscribe(params => {
      this.postId = params['id'];
      console.log(this.postId);

      const client = contentful.createClient({
        space: '****',
        accessToken: '****',
      });

      client.getEntry(this.postId).then(entry => {
        this.post = entry.fields;
        
        this.tags = entry.metadata.tags
        console.log(this.tags[0].sys.id);
        
      }).catch(error => {
        console.log(error);
      });
    });
  }
}

模板代码:

<div class="container" *ngIf="post">
    <div class="row">
        <div class="col-md-8 col-lg-8">
            <article class="article-post">
                <div class="article-header">
                    <h1 class="article-title" *ngIf="post.title">{{post.title}}</h1>
                    <img class="article-img" src={{post.image?.fields.file.url}} alt="">
                    <div class="article-meta">
                        <span class="meta-date"><i class="far fa-clock"></i> May 20, 2023</span>
                        <span class="meta-category " *ngFor="let tag of tags"> <i class="far fa-folder"></i> {{tag?
                            tag.sys.id : 'No tags'}}</span>
                    </div>
                </div>
                <div class="article-content">
                    <h2>{{post.section1Title}}</h2>
                    <p>{{post.description.content[0].content[0].value}}</p>
                    <!-- section 1 explained desc -->
                    <p>{{post.section1Info.content[0].content[0].value}}</p>
                    <!-- section 2 my thoughts -->

                    <h2>{{post.section2Title}}</h2>
                    <p>{{post.section2Experience.content[0].content[0].value}}</p>
                    <!-- section 3 info -->

                    <h2>{{post.section3Title}}</h2>
                    <p>{{post.section3UseCase.content[0].content[0].value}}</p>
                    <blockquote>
                        <p>{{post.blockquote1.content[0].content[0].value}}</p>
                    </blockquote>
                    <p><sub>{{post.section4Title}}</sub></p>
                    <p>{{post.section4Facts.content[0].content[0].value}}</p>
                    <a href="{{post.section4Facts.content[1].content[1].content[0].value}}">MDN library</a>

                    <!-- <ul *ngIf="post.table !== undefined">
                        <li *ngFor="let li of post.table.content[0].content[1].content">
                            {{li.content[0].content[0].data.value}}</li>
                        <li></li>
                        <li></li>
                        <li></li>
                        <li></li>
                    </ul> -->
                </div>
            </article>
        </div>
        <div class="col-md-4 col-lg-4">
            <aside class="sidebar">
                <div class="widget">
                    <h5 class="widget-title">About Me</h5>
                    <div class="widget-content">
                        <a href="/about">Hello there! My name is Boyan, and I'm delighted to welcome you to my online
                            development blog.</a>
                    </div>
                </div>
                <div class="widget">
                    <h5 class="widget-title">Categories</h5>
                    <div class="widget-content">
                        <ul class="list-unstyled">
                            <li><a href="#">Technology</a></li>
                            <li><a href="#">Web Development</a></li>
                            <li><a href="#">Design</a></li>
                            <li><a href="#">Photography</a></li>
                            <li><a href="#">Miscellaneous</a></li>
                        </ul>
                    </div>
                </div>
                <div class="widget">
                    <h5 class="widget-title">Tags</h5>
                    <div class="widget-content">
                        <ul class="list-unstyled tag-cloud">
                            <li><a href="#">Bootstrap</a></li>
                            <li><a href="#">HTML</a></li>
                            <li><a href="#">CSS</a></li>
                            <li><a href="#">JavaScript</a></li>
                            <li><a href="#">Web Design</a></li>
                            <li><a href="#">Development</a></li>
                        </ul>
                    </div>
                </div>
            </aside>
        </div>
    </div>
</div>

一、数据规整实现方案

1. 组件内编写解析函数预处理数据

在组件中添加通用解析函数,提前把嵌套的Rich Text字段转换成易读的格式,避免模板里写多层索引:

// 提取Rich Text中的纯文本内容
getRichTextText(richText: any): string {
  if (!richText?.content?.length) return '';
  return richText.content
    .map((block: any) => block.content
      .map((inline: any) => inline.content
        .map((node: any) => node.value).join('')
      ).join('')
    ).join('\n');
}

// 提取Rich Text中的链接值
getRichTextLink(richText: any, blockIndex: number = 0, inlineIndex: number = 0): string {
  if (!richText?.content?.length) return '';
  const targetNode = richText.content[blockIndex]?.content[inlineIndex]?.content[0];
  return targetNode?.value || '';
}

然后在获取数据后直接规整:

client.getEntry(this.postId).then(entry => {
  const rawFields = entry.fields;
  // 生成规整后的post对象
  this.post = {
    title: rawFields.title,
    imageUrl: rawFields.image?.fields.file.url,
    section1Title: rawFields.section1Title,
    description: this.getRichTextText(rawFields.description),
    section1Info: this.getRichTextText(rawFields.section1Info),
    section2Title: rawFields.section2Title,
    section2Experience: this.getRichTextText(rawFields.section2Experience),
    section3Title: rawFields.section3Title,
    section3UseCase: this.getRichTextText(rawFields.section3UseCase),
    blockquote1: this.getRichTextText(rawFields.blockquote1),
    section4Title: rawFields.section4Title,
    section4FactsText: this.getRichTextText(rawFields.section4Facts),
    section4FactsLink: this.getRichTextLink(rawFields.section4Facts, 1, 1)
  };
  this.tags = entry.metadata.tags;
}).catch(error => {
  console.log(error);
});

模板里就可以直接用规整后的字段:

<p>{{post.description}}</p>
<a href="{{post.section4FactsLink}}">MDN library</a>

2. 用Angular管道实现复用解析逻辑

如果多个组件需要处理Contentful数据,写一个自定义管道更高效:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({ name: 'contentfulRichText' })
export class ContentfulRichTextPipe implements PipeTransform {
  transform(richText: any, type: 'text' | 'link' = 'text', indices?: [number, number]): string {
    if (!richText?.content?.length) return '';
    
    if (type === 'text') {
      return richText.content
        .map((block: any) => block.content
          .map((inline: any) => inline.content
            .map((node: any) => node.value).join('')
          ).join('')
        ).join('\n');
    }
    
    if (type === 'link' && indices) {
      const [blockIdx, inlineIdx] = indices;
      const targetNode = richText.content[blockIdx]?.content[inlineIdx]?.content[0];
      return targetNode?.value || '';
    }
    
    return '';
  }
}

模板中直接调用管道:

<p>{{post.description | contentfulRichText}}</p>
<a href="{{post.section4Facts | contentfulRichText:'link':[1,1]}}">MDN library</a>

3. 定义TypeScript类型增强代码可读性

给规整后的Post定义接口,避免any类型带来的不确定性:

interface FormattedPost {
  title: string;
  imageUrl: string;
  section1Title: string;
  description: string;
  section1Info: string;
  section2Title: string;
  section2Experience: string;
  section3Title: string;
  section3UseCase: string;
  blockquote1: string;
  section4Title: string;
  section4FactsText: string;
  section4FactsLink: string;
}

// 组件内声明post类型
post: FormattedPost | undefined;

二、实用技术资源

  • Contentful Rich Text结构文档:虽然官网没有直接的规整方案,但可以参考Rich Text的节点层级说明(block、inline、text节点的关系),理解嵌套逻辑后更容易写解析函数。
  • 官方渲染工具:使用@contentful/rich-text-html-renderer包,直接将Rich Text转为HTML字符串,无需手动解析嵌套结构,安装后调用:
    import { documentToHtmlString } from '@contentful/rich-text-html-renderer';
    
    // 直接转换为HTML
    const descriptionHtml = documentToHtmlString(rawFields.description);
    
    模板中用[innerHTML]="descriptionHtml"渲染即可。

内容的提问来源于stack exchange,提问作者Boyan Iliev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 07:17:01