How do I read local nested json?

Hi,
I have trying to work this out for weeks, looking all over the Internet and I can’t seem to find a solution.

How can I print from my .json file: year name and course name? Do I have to bind career.id and year.id together so I can show that career year courses? How could I do that? Thanks in advance!

.ts file

import { Component, OnInit } from '@angular/core';
import { MenuController, NavController } from '@ionic/angular';
import { Router } from '@angular/router';

@Component({
  selector: 'app-carreras',
  templateUrl: './carreras.page.html',
  styleUrls: ['./carreras.page.scss'],
})
export class CarrerasPage implements OnInit {

  carrera: any;

  constructor(
    public menu: MenuController,
    public router: Router,
    public navContrller: NavController,
  ) { }

  ngOnInit() {
    fetch('../../../assets/data/BDs/carreras.json').then(res => res.json()).then(json => {
      this.carrera = json;
    });
  }

}

.html file

//This works
    <ion-item class="carreerSelectionButton">
        <ion-label position="floating">Carrera</ion-label>
        <ion-select interface="action-sheet" placeholder="Seleccione su carrera" cancel-text="Cancelar">
            <ion-select-option *ngFor="let c of carrera; index as i">{{c.name}}</ion-select-option>
        </ion-select>
    </ion-item>

/This doesn't work
    <ion-item class="yearSelectionButton">
        <ion-label position="floating">Año</ion-label>
        <ion-select interface="action-sheet" placeholder="Seleccione su carrera" cancel-text="Cancelar">
            <ion-select-option *ngFor="let c of carrera; index as i">{{c.year.name}}</ion-select-option>
        </ion-select>
    </ion-item>

.json file

[{
        "id": "LCC",
        "name": "Licenciatura en Ciencias de la Computación",
        "year": [{
                "id": 1,
                "name": "Primer Año",
                "quarter": [{
                        "id": 1,
                        "course": [{
                                "id": "EA",
                                "name": "Elementos de Álgebra"
                            },
                            {
                                "id": "RPA",
                                "name": "Resolución de Problemas y Algoritmos"
                            },
                            {
                                "id": "IC",
                                "name": "Introducción a la Computación"
                            },
                            {
                                "id": "MSI",
                                "name": "Modelados y Sistemas de la Información"
                            }
                        ]
                    },
                    {
                        "id": 2,
                        "course": [{
                                "id": "DDA",
                                "name": "Desarrollo de Algoritmos"
                            },
                            {
                                "id": "EAL",
                                "name": "Elementos de Álgebra Lineal"
                            },
                            {
                                "id": "ETC",
                                "name": "Elementos de Teoría de la Computación"
                            },
                            {
                                "id": "MODATOS",
                                "name": "Modelados de Datos"
                            }
                        ]
                    }
                ]
            },

I find it a very helpful exercise to define interfaces for every business object in an app, and I think naming is very important. Things that are arrays get plural names; things that are scalars get singular names. This means that the data you have looks like this:

interface Course {
  id: string;
  name: string;
}

interface Quarter {
  id: number;
  courses: Course[];
}

interface Year {
  id: number;
  name: string;
  quarters: Quarter[];
}

type Career = Year[];  

So, in English, a Career is a list of Years, a Year has a list of Quarters; a Quarter has a list of Courses.

Whether that’s a good thing or not, I have no way of judging, but you should take the time to think long and hard about that, because by far the simplest and most far-reaching thing you can do here is to make sure your data is structured exactly the way you want it. If it isn’t, fix the JSON before you do anything else. If the JSON is coming from an external source, write code to munge it into the desired format. Don’t do anything Ionic-related until you’ve sorted out having the data be in the format you want to work with.

If you’re going to change anything about how it’s structured, feel free to stop reading right here, because nothing else I say will be relevant.

Now that you’ve decided that the data format you have is the data format you want, then there is a fundamental problem with your template in that c.year does not uniquely specify a Year. It is a list. You need to decide which year you want to display, and if the answer is “all of them”, then you need a second loop inside the existing one.

Incidentally, don’t bind index unless you’re going to use it, and avoid needing to use it if you can, because that eliminates an entire class of bugs involving what happens when you modify different (or aliased) copies of data.

1 Like

Hello @rapropos,
Thank you for your help. I’ve thought about changing the JSON and with help of partners we’ve changed it to what I’m showing. As of right now, my JSON is being fetched locally so I can have little bit of practice working with JSONs, in a few weeks we are going to fetch it from a REST API so maybe I could have more questions :stuck_out_tongue:.

Last night, on Stack Overflow someone answered me this same question and told me to change my code like below (also I removed index like you told me to):

//This works
    <div>
        <ion-item class="carreerSelectionButton">
            <ion-label position="floating">Carrera</ion-label>
            <ion-select interface="action-sheet" placeholder="Seleccione su carrera" cancel-text="Cancelar">
                <ion-select-option *ngFor="let c of carrera">{{c.name}}</ion-select-option>
            </ion-select>
        </ion-item>
    </div>

//This doesn't work
    <div *ngFor="let c of carrera">
        <ion-item class=" yearSelectionButton ">
            <ion-label position="floating">Año</ion-label>
            <ion-select interface="action-sheet" placeholder="Seleccione su carrera" cancel-text="Cancelar">
                <ion-select-option *ngFor="let yName of c.year">{{yName.name}}</ion-select-option>
            </ion-select>
        </ion-item>
    </div>

This way I can show years of careers, the thing is that it makes one drop down menu for each career year selection and that it doesn’t matter which career I select, each one of this drop down menus will print the amount of years of each one of the other careers. Ex, let’s say I have 2 careers, one has 5 years and the other one has 3 years. The first drop down menu prints 5 selections and the other one shows 3 years. Is there a way I can show just one drop down menu for years and that it only prints the amount of years for the previously selected career?

Edit: here is an image how it’s being shown:

What I am hearing here is that you want the concept of “year” to declare independence from any given “career”. If that’s not true, stop reading.

In this case, the structure of the data, while it may be useful and convenient to produce, is not useful or convenient to consume, because a Year has no meaningful existence outside of a Career.

So here’s how I would structure this:

  • a bunch of Courses
  • a Year, which has a name
  • a Quarter, which has a name and number
  • a set of Careers, each of which has a name and a list of Courses that are offered in a given Year and Quarter.

These are our building blocks, so I would want the JSON to start out looking like:

{
  "courses": [ {"id": "C-EA", "name": "Elementos de Álgebra" }, ... ],
  "quarters": [ {"number": 1, "name": "Primer Quarter"}, ...],
  "years": [{"number": 1, "name": "Primer Año"}, ...)],
  "careers": [{"id": "M-LCC", name: "Licenciatura en Ciencias de la Computación"}, ...]
}

Note that ids are globally unique: I prefixed Course ids with “C-”, and Career ids with “M-” (from the English “major” which is what I think your “career” concept is often referred to as). This is important, although the mechanism via which you guarantee global uniqueness is up to you.

Now we need a new concept: a set of Courses that are offered in a given Quarter of a given Year to students in a given Career. I’ll call it a Slate:

interface Slate {
  careerId: string;
  yearNumber: number;
  quarterNumber: number;
  courseIds: string[];
}

These also get added to the JSON:

slates: [
  {"careerId": "M-LCC", "yearNumber": 1, "quarterNumber": 1, "courseIds": ["C-EA", "C-RPA", "C-IC", "C-MSI"]},
  {"careerId": "M-LCC", "yearNumber": 1, "quarterNumber": 2, "courseIds": ["C-DDA", "C-EAL", "C-ETC", "C-MODATOS"]},
  ...
]

Note that this gives you the flexibility to offer the same course in many contexts, and without duplicating any other course metadata (instructor, credits, whatever).

This design should allow you to easily make a form that allows selecting Careers and Years and Quarters independently.