Can't get TypeScript to import JSON with proper types

I have the following file test.json that I wish to import it in a typed form.

{
  "items": [
    {
      "kind": "youtube#video",
      "thumbnails": {
        "default": {
          "url": "https://i.ytimg.com/vi/WnVAkK876rA/default.jpg",
          "width": 120,
          "height": 90
        }
      }
    },
    {
      "kind": "youtube#video",
      "thumbnails": {
        "default": {
          "url": "https://i.ytimg.com/vi/jhTpc7nFtfI/default.jpg",
          "width": 120,
          "height": 90
        },
        "maxres": {
          "url": "https://i.ytimg.com/vi/jhTpc7nFtfI/maxresdefault.jpg",
          "width": 1280,
          "height": 720
        }
      }
    }
  ]
}

This is my App.tsx:

import { TestData } from './Types';
import * as testData from './test.json'
const ttt: TestData = testData;

This is Types.ts

export interface TestData {
  items: TestDataPiece[]
}

export interface TestDataPiece {
  kind: string,
  thumbnails: {[key: string]: {
    url: string,
    width: number,
    height: number
  }}
}

In my tsconfig.json I use "resolveJsonModule": true. However I get this error:

[tsl] ERROR in C:\Users\home\IdeaProjects\yt-glasat\src\App.tsx(11,7)
      TS2322: Type '{ items: ({ kind: string; thumbnails: { default: { url: string; width: number; height: number; }; maxres?: undefined; }; } | { kind: string; thumbnails: { default: { url: string; width: number; height: number; }; maxres: { ...; }; }; })[]; }' is not assignable to type 'TestData'.
  Types of property 'items' are incompatible.
    Type '({ kind: string; thumbnails: { default: { url: string; width: number; height: number; }; maxres?: undefined; }; } | { kind: string; thumbnails: { default: { url: string; width: number; height: number; }; maxres: { ...; }; }; })[]' is not assignable to type 'TestDataPiece[]'.
      Type '{ kind: string; thumbnails: { default: { url: string; width: number; height: number; }; maxres?: undefined; }; } | { kind: string; thumbnails: { default: { url: string; width: number; height: number; }; maxres: { ...; }; }; }' is not assignable to type 'TestDataPiece'.
        Type '{ kind: string; thumbnails: { default: { url: string; width: number; height: number; }; maxres?: undefined; }; }' is not assignable to type 'TestDataPiece'.
          Types of property 'thumbnails' are incompatible.
            Type '{ default: { url: string; width: number; height: number; }; maxres?: undefined; }' is not assignable to type '{ [key: string]: { url: string; width: number; height: number; }; }'.
              Property '"maxres"' is incompatible with index signature.
                Type 'undefined' is not assignable to type '{ url: string; width: number; height: number; }'.

The problem is that the inferred type of the data has a maxres property that's optional, which means its type is {url: string; width: number; height: number} | undefined. But your thumbnail object type doesn't allow undefined for the values of the object with the index signature.

Any of these would make the error go away:

  1. Ensure that all of the relevant objects in the JSON have a maxres property; or
  2. Allow undefined on thumbnail objects; or
  3. Use a type assertion when doing the assignment, perhaps backed by a runtime check

I'm guessing you can't do #1 and don't want to do #2, so you're left with the type assertion:

const ttt = testData as TestData;

Type assertions are generally a last-ditch solution, though; you can back it up with a runtime check to do validation on testData to ensure it fits the interface using a type guard function or a type assertion function.

For instance:

function assertIsValidTestData(data: any): asserts data is TestData {
    if (!Array.isArray(data) ||
        data.some(element => {
            return typeof element !== "object" ||
                   typeof element.kind !== "string" ||
                   typeof element.thumbnails !== "object" ||
                   Object.values(element.thumbnails).some(thumbnail => {
                       return typeof thumbnail !== "object" ||
                              typeof thumbnail.url !== "string" ||
                              typeof thumbnail.width !== "number" ||
                              typeof thumbnail.height !== "number";
                   })
        })) {
        throw new Error(`Test data is not valid`);
    }
}

And then

assertIsValidTestData(testData);
const ttt: TestData = testData;

Using a type assertion should work for your case:

const ttt = testData as TestData;