简体   繁体   中英

Understanding typescript interfaces

I am tying to understand format of typescript/javascript and need help in understanding certain steps here. I am parsing a JSON file into an object and then trying to read values to update some values.

Thanks to Timmy chan I got some pointers from my previous post here, now building on that. Objects creation using interface for JSON types in typescript

Say I have this interface defined,

interface AllData {
  [value:string]:Row 
}

interface Row {
  eDate: string;
  mystate: string;
  mystateCity: string;
  numberofpeople: number;
}

let Data: AllData ;

I read the file like this and assign it to Data variable.

 const response = await fetchFile(fileName);
    Data= await response.json();

Now I want to create a new object which will only have rows

 const CountData: Row[] = {} // this gives error Type '{}' is missing the following properties from type 'CountyGraphRow[]': length, pop, push,
 
if I change it to 
const CountData: Row[] = []  // when to have {} or [].. What is the difference?

  for (const rowData in Data["value"]) 
  {
    console.log(rowData); //  this is coming out 0
    CountData.push({  //  error at TypeError: Cannot read property 'eDate' of undefined
      eDate: Data[rowData].eDate,  // Something is wrong in the way I am accessing them.
      mystate: Data[rowData].mystate,
      mystateCity: Data[rowData].mystateCity,
      numberofpeople: Data[rowData].numberofpeople > 20? 20 : Data[rowData].numberofpeople < 5? 5: Data[rowData].numberofpeople,
    })
  }

Here is how the file looks like

{
  "value": [
    {
      "eDate": "2020-03-01T00:00:00.000Z",
      "mystate": "state1",
      "mystateCity": "state1, ID",
      "numberofpeople": 2.973529602
    },
    {
      "eDate": "2020-03-02T00:00:00.000Z",
      "mystate": "state1",
      "mystateCity": "state1, ID",
      "numberofpeople": 2.973529602
    },
    {
      "eDate": "2020-03-03T00:00:00.000Z",
      "mystate": "state1",
      "mystateCity": "state1, ID",
      "numberofpeople": 2.973529602
    }
]}

--------------Update 2-------------

interface AllData {
    [value: string]: Row
}

interface Row {
    eDate: string;
    mystate: string;
    mystateCity: string;
    numberofpeople: number;
}

let Data: AllData;

Data = JSON.parse(`{ 
  "value": [
    {
      "eDate": "2020-03-01T00:00:00.000Z",
      "mystate": "state1",
      "mystateCity": "state1, ID",
      "numberofpeople": 2.973529602
    },
    {
      "eDate": "2020-03-02T00:00:00.000Z",
      "mystate": "state1",
      "mystateCity": "state1, ID",
      "numberofpeople": 2.973529602
    },
    {
      "eDate": "2020-03-03T00:00:00.000Z",
      "mystate": "state1",
      "mystateCity": "state1, ID",
      "numberofpeople": 2.973529602
    }
]}`);

const CountData: Row[] = [];

for (const rowData in Data["value"]) { // Data["value"] I am assuming this //will pick all objects   "value": [
   // {
     // "eDate": "2020-03-01T00:00:00.000Z",
// please correct me if I am wrong

   

 console.log(rowData); //  this is coming out 0
    CountData.push({  //  error at TypeError: Cannot read property 'eDate' of undefined
        eDate: Data[rowData].eDate,  // Something is wrong in the way I am accessing them.
        mystate: Data[rowData].mystate,
        mystateCity: Data[rowData].mystateCity,
        numberofpeople: Data[rowData].numberofpeople > 20 ? 20 : Data[rowData].numberofpeople < 5 ? 5 : Data[rowData].numberofpeople,
    })
}

Oh, the issue with {} vs [] : the former is an empty object and the latter is an empty array. In JavaScript, arrays are all objects but not all objects are arrays. Specifically, arrays have all the array methods like push() that you care about. If CountData is supposed to be an array, you can't assign {} to it, because it lacks the things arrays need.

Moving on the main part of your question:


The first problem here is that your interface

interface AllData {
  [value:string]:Row 
}

has a string index signature , which means "an AllData can have any number of keys of any string value whatsoever, and the property at each of those keys will be a single Row object".

Please note that the value in the above definition is just a placeholder and has no effect whatsoever on the type. It is the same exact definition as

interface AllData {
  [x:string]:Row 
}

Anyway, your problem is that the Data you are reading does not have that shape. It has a single key of exactly the name "value" , and the property at that key is an array of Row objects. Meaning the following:

let Data: { value: Row[] };

Once you fix that, let's look at the smallest possible change to your code to get it working, and then improve from there:

const CountData: Row[] = [];
for (const rowData in Data["value"]) {
    CountData.push({
        eDate: Data["value"][rowData].eDate,
        mystate: Data["value"][rowData].mystate,
        mystateCity: Data["value"][rowData].mystateCity,
        numberofpeople: Data["value"][rowData].numberofpeople > 20 ? 20 :
          Data["value"][rowData].numberofpeople < 5 ? 5 : 
          Data["value"][rowData].numberofpeople,
    })
}

The loop for (const rowData in Data["value"]) iterates over the keys of the Data["value"] array. These are going to be the string values corresponding to the array indices: "0" , "1" , "2" , etc. It is generally not recommended to iterate over arrays via for..in , because you'll get the indices as strings, possibly out of order, and with possible other unexpected things in there. Still, it sort of works.

The main issue here, though: since you are iterating over Data["value"] 's keys, you need to read its properties by indexing into Data["value"] , not Data .


Let's stop iterating over array indices with for..of and use a regular for loop:

const CountData: Row[] = [];
for (let i = 0; i < Data["value"].length; i++) {
    CountData.push({
        eDate: Data["value"][i].eDate,
        mystate: Data["value"][i].mystate,
        mystateCity: Data["value"][i].mystateCity,
        numberofpeople: Data["value"][i].numberofpeople > 20 ? 20 : 
          Data["value"][i].numberofpeople < 5 ? 5 : Data["value"][i].numberofpeople,
    })
}

That is now guaranteed to happen in order and without any weird non-numeric indices.


Next improvements: let's stop saying Data["value"] since there's no reason to use bracket property access with a string literal that's a valid JavaScript identifier. You can just use Data.value .

Also, instead of writing Data.value[i] a bunch of times, just save it to its own variable (hey we can call that rowData now):

const CountData: Row[] = [];
for (let i = 0; i < Data.value.length; i++) {
    const rowData = Data.value[i]; 
    CountData.push({
        eDate: rowData.eDate,
        mystate: rowData.mystate,
        mystateCity: rowData.mystateCity,
        numberofpeople: rowData.numberofpeople > 20 ? 20 : 
          rowData.numberofpeople < 5 ? 5 : rowData.numberofpeople,
    })
}

Next, you might consider using a for...of loop to directly iterate over the rowData element s without having to care about their numeric indices :

const CountData: Row[] = [];
for (const rowData of Data.value) { 
    CountData.push({
        eDate: rowData.eDate,
        mystate: rowData.mystate,
        mystateCity: rowData.mystateCity,
        numberofpeople: rowData.numberofpeople > 20 ? 20 : 
          rowData.numberofpeople < 5 ? 5 : 
          rowData.numberofpeople,
    });
}

}


Next, copying individual properties is fine, but if you're going to copy all or most of the properties, you can use object literal spread syntax to do it quickly (or Object.assign() which is similar) and then just rewrite the one property you want to change:

const CountData: Row[] = [];
for (const rowData of Data.value) {
    CountData.push({ 
        ...rowData,
        numberofpeople: rowData.numberofpeople > 20 ? 20 : 
          rowData.numberofpeople < 5 ? 5 : rowData.numberofpeople
    })
}

}


Finally, if all you're doing is walking over one array and producing another array of the exact same size with each element in the input producing a single element in the output, you can dispense with looping and push ing entirely and use Array.prototype.map() instead:

const CountData = Data.value.map(rowData => ({
    ...rowData,
    numberofpeople: rowData.numberofpeople > 20 ? 20 : 
      rowData.numberofpeople < 5 ? 5 : rowData.numberofpeople
}));

}


Whew! I think that's as good as I can make it.

Playground link to code

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM