如何访问嵌套在 json 数据中的 objects 的属性和值?

How to access properties and values of objects nested in json data?

模型如下:一本书包含几个片段,每个片段包含章节,章节又包含诗句。

对于片段,属性 感兴趣的是标题。 对于经文,感兴趣的属性是经文编号和经文文本。 (用户对章节数据不感兴趣)。

相关型号代码如下:

Fragments.ts:

import {Deserializable} from './deserializable';
import { Chapter } from './chapter';
import { Verse } from './verse';

export class Fragment implements Deserializable {
    public id?: number;
    public url?: string;
    public surtitle?: string;
    public title_string?: string;
    public title_format?: number;
    public verses?: Verse;
    public chapter_id?: Chapter;

  deserialize(input: any): this {
      Object.assign(this, input);
      return this;
    }

chapters.ts:

import {Deserializable} from './deserializable';
import { Livre } from './livre';

export class Chapter implements Deserializable {
    public id?: number;
    public url?: string;
    public number?: number;
    public book_id?: Livre;

  deserialize(input: any): this {
      Object.assign(this, input);
      return this;
    }
  }

verse.ts:

import {Deserializable} from './deserializable';
import { Fragment } from './fragment';

export class Verse implements Deserializable {
    public id?: number;
    public url?: string;
    public number?: number;
    public text?: string;
    public optional_indication?: number;
    public fragment_id?: Fragment;

  deserialize(input: any): this {
      Object.assign(this, input);
      return this;
    }
}

目标是在网页中向用户显示一本书的内容:即片段的标题,然后是它的经文,然后是下一个片段的标题,然后是它的经文等。

目前,名为“livre-detail.component.ts”的相关组件中的代码获取一本书的全部内容,包括片段和嵌套数据,直至每一节的文本“此片段”和 JSON 数据已正确记录在控制台中,或者当模板简单地显示在浏览器中时:

<div *ngFor= 'let fragment of fragments'>
  {{ fragment | json}}
</div>

在模板中,当代码使用 *ngFor 指令循环遍历片段时,每个片段的标题都会正确显示 ("fragment.title_string")。

但我想不出一个嵌套循环,导致在每个片段中显示每节经文的文本。

我试过很多东西:

这是我当前的代码:

livre-detail-component.ts:

import { Component, OnInit, Input } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Observable, from} from 'rxjs';
import {map} from 'rxjs/operators';
import { Fragment } from '../models/fragment';
import { Verse } from '../models/verse';
import {ResponseApi} from '../models/api';
import { FragmentService } from '../services/fragment.service';


@Component({
  selector: 'app-livre-detail',
  templateUrl: './livre-detail.component.html',
  styleUrls: ['./livre-detail.component.scss']
})
export class LivreDetailComponent implements OnInit {

  fragments$!: Observable<Fragment[]>;

  fragment: Fragment | undefined;
  fragments: Fragment[] | undefined;
  
  verse: Verse | undefined;
  //verses: Verse[] | undefined;
  text: String | undefined;

  // verseId: number | undefined;

  constructor(
    private route: ActivatedRoute,
    private fragmentService: FragmentService,  
  ) { }

  ngOnInit() {

        // First get the book diminutive from the current route.
        const routeParams = this.route.snapshot.paramMap;
        const bookDiminutiveFromRoute = String(routeParams.get('bookDiminutive'));
    
        // Find the fragments that belong to the book with the diminutive provided in route.
        // Note: a fragment belongs to a chapter which in turn belongs to a route.
        this.fragments$ = this.fragmentService.filterList(
          'chapter_id__book_id__diminutive', bookDiminutiveFromRoute).pipe(
          map((responseApi: ResponseApi<Fragment>) => {
            console.log(responseApi.results)
            return responseApi.results;
          })
        );
        this.fragments$.subscribe((fragments: Fragment[]) => {
          this.fragments = fragments;
          console.log(this.fragments)
        });
      }

livre-detail-component.html:

    <div *ngFor= 'let fragment of fragments'>
      <h3>{{ fragment.title_string }}</h3>
      {{fragment.verses}}
    </div>

以上returns "[objectObject],[objectObject],[objectObject],[objectObject],[object Object],[object Object],[object Object],[object Object] " 在每个片段标题下:

备选方案 1:具有嵌套循环的模板,如下所示:

    <div *ngFor= 'let fragment of fragments'>
      <h3>{{ fragment.title_string }}</h3>
        <div>
          <div *ngFor= 'let verse of fragment.verses'>
            {{ verse.text }}
        </div>
    </div>

此returns以下错误信息:

"Type 'Verse | undefined' is not assignable to type 'any[] | Iterable | (Iterable & any[]) | (any[] & Iterable) | null | undefined'."

备选方案 2:尝试在组件文件中嵌套映射:

import { Component, OnInit, Input } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Observable, from} from 'rxjs';
import {map} from 'rxjs/operators';
import { Fragment } from '../models/fragment';
import { Verse } from '../models/verse';
import {ResponseApi} from '../models/api';
import { FragmentService } from '../services/fragment.service';


@Component({
  selector: 'app-livre-detail',
  templateUrl: './livre-detail.component.html',
  styleUrls: ['./livre-detail.component.scss']
})
export class LivreDetailComponent implements OnInit {

  fragments$!: Observable<Fragment[]>;

  fragment: Fragment | undefined;
  fragments: Fragment[] | undefined;
  
  verse: Verse | undefined;
  verses: Verse[] | undefined;
  text: String | undefined;

  verseId: number | undefined;

  constructor(
    private route: ActivatedRoute,
    private fragmentService: FragmentService,  
  ) { }

  ngOnInit() {

        // First get the book diminutive from the current route.
        const routeParams = this.route.snapshot.paramMap;
        const bookDiminutiveFromRoute = String(routeParams.get('bookDiminutive'));
    
        // Find the fragments that belong to the book with the diminutive provided in route.
        // Note: a fragment belongs to a chapter which in turn belongs to a route.
        this.fragments$ = this.fragmentService.filterList(
          'chapter_id__book_id__diminutive', bookDiminutiveFromRoute).pipe(
          map((responseApi: ResponseApi<Fragment>) => {
            console.log(responseApi.results)
            return responseApi.results;
          })
        );

        this.fragments = fragments.map((fragment: Fragment)=>{
          let verseObjects = this.verses.map((verseId: number) =>{
            return this.verses?.find((verse, index) => {return index === verseId})
          });
          fragment.verses = verseObjects;
          return fragment.verses          
        }); 
  }

这会触发以下错误消息:

Error: src/app/livre-detail/livre-detail.component.ts:54:11 - error TS2741: Property 'deserialize' is missing in type '(Verse | undefined)[]' but required in type 'Verse'.

54 fragment.verses = verseObjects; ~~~~~~~~~~~~~~~

src/app/models/verse.ts:13:3 13 deserialize(input: any): this { ~~~~~~~~~~~ 'deserialize' is declared here. "

作为参考,verse.ts 包含 Verse 的模型如下:

import {Deserializable} from './deserializable';
import { Fragment } from './fragment';

export class Verse implements Deserializable {
    public id?: number;
    public url?: string;
    public number?: number;
    public text?: string;
    public optional_indication?: number;
    public fragment_id?: Fragment;

  deserialize(input: any): this {
      Object.assign(this, input);
      return this;
    }
}

也供参考,deserializable.ts如下:

export interface Deserializable {
    deserialize(input: any): this;
  }

如有任何帮助,我们将不胜感激。

最后,经过反复试验,我解决了如下问题:

  1. 我通过将 Verse 设为数组来编辑 fragment.ts 中的 Fragment 模型。行
public verses?: Verse;

已替换为

public verses?: Verse[];
  1. 我还在后端编辑了代码库(使用 Django Rest Framework):我将字段“fragments”添加到 Chapter 序列化程序,以及对 Fragment 序列化程序的显式引用。因此,我还必须在 serializer.py 文件中将 Fragment 序列化器移动到 Chapter 序列化器之上,因为现在 Chapter 序列化器指的是 FragmentSerializer class。这样,请求与 ChapterList 视图(继承自 generics.ListAPIView)关联的 url 返回一个嵌套的 json 文件,不仅包括每一章下的每个片段,还包括每个片段下的每一节经文,所以双层嵌套,以前不是这样的。
class FragmentSerializer(serializers.ModelSerializer):
    """
    Fragment in a chapter
    Returns chapter and book objects upward
    as well as all verses downward (reverse relationship) 
    """

    class Meta:
        model = Fragment
        depth = 2
        fields = [
            'id',
            'chapter_id',
            'surtitle',
            'title_string',
            'title_format',
            'verses',
        ]


class ChapterSerializer(serializers.ModelSerializer):
    """
    Chapters
    """
    # https://www.django-rest-framework.org/api-guide/serializers/#dealing-with-nested-objects
    fragments = FragmentSerializer(many=True)
    class Meta:
        model = Chapter
        depth = 1 # allows access to the whole book object
        fields = ['id', 'number', 'book_id', 'fragments',]

  1. 现在,在 livre-detail.component.html 文件中,每一节 属性 都可以使用点符号访问:
<div *ngFor= 'let chapter of chapters' >
  <span class="chapter_number">
    Chapitre {{ chapter.number }}
  </span>
  <p></p>
  <div *ngFor= 'let fragment of chapter.fragments'>
    <p>{{ fragment.title_string }}</p>
        <div *ngFor= 'let verse of fragment.verses'>
            {{ verse.number }}
            {{ verse.text }}     
        </div>
        <p></p>          
  </div>
</div>

  1. 最终干净的打字稿文件 livre-detail.component.ts 是:
import { Component, OnInit, Input } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Observable, from} from 'rxjs';
import {map} from 'rxjs/operators';
import { Livre } from '../models/livre';
import { Chapter } from '../models/chapter';
import { Fragment } from '../models/fragment';
import {ResponseApi} from '../models/api';

import { LivreService } from '../services/livre.service';
import { ChapterService} from '../services/chapter.service';


@Component({
  selector: 'app-livre-detail',
  templateUrl: './livre-detail.component.html',
  styleUrls: ['./livre-detail.component.scss']
})
export class LivreDetailComponent implements OnInit {

  livres$!: Observable<Livre[]>;
  livres: Livre[] | undefined;
  livre: Livre | undefined;
  livre_name_ordinal: number | undefined;
  livre_name_text: string | undefined;
  
  chapters$!: Observable<Chapter[]>;
  chapters: Chapter[] | undefined;
  chapter: Chapter | undefined;
  chapter_number_as_string: string | undefined;
  
  fragments$!: Observable<Fragment[]>;
  fragment: Fragment | undefined;
  fragments: Fragment[] | undefined;
  fragment_id: number | undefined;
  fragment_id_as_string: string | undefined;
  title_string: string | undefined;

  constructor(
    private route: ActivatedRoute,
    private livreService: LivreService,
    private chapterService: ChapterService,
  ) { }

  ngOnInit(): void {
        // First get the book diminutive from the current route.
        const routeParams = this.route.snapshot.paramMap;
        const bookDiminutiveFromRoute = String(routeParams.get('bookDiminutive'));
        console.log(bookDiminutiveFromRoute)

        // Get the name of the book whose detail is displayed on this page
        this.livres$ = this.livreService.filterList(
          'diminutive', bookDiminutiveFromRoute).pipe(
            map((responseApi: ResponseApi<Livre>) => {
              return responseApi.results;
            }
          )
        );
        this.livres$.subscribe((livres: Livre[]) => {
          this.livres = livres;
          // Actually a single book gets returned, so:
          this.livre = this.livres[0]
          this.livre_name_ordinal = this.livre.name_ordinal
          this.livre_name_text = this.livre.name_text
        });

        // List chapters that belong to the book being displayed
        this.chapters$ = this.chapterService.filterList(
          'book_id__diminutive', bookDiminutiveFromRoute).pipe(
            map((responseApi: ResponseApi<Livre>) => {
              return responseApi.results;
            }
          )
        );
        this.chapters$.subscribe((chapters: Chapter[]) => {
          this.chapters = chapters
        });
      }
    }

很高兴听到任何改进该解决方案的方法。