Search code examples
javascripttypescriptvegavega-lite

How to cast string to complex and nested interface in Typescript?


I am migrating Javascript to Typescript.

I have the following code from this npm pacakage.

I want to convert spec (as string) to type VisualizationSpec so that <VegaLite spec={spec} data={barData} />, works.

I tried cast but it does not work. This interface is nested and complex, so I failed.

How to cast string to complex and nested interface in Typescript?

import React from 'react'
import ReactDOM from 'react-dom'
import { VegaLite } from 'react-vega'

const spec = {
  width: 400,
  height: 200,
  mark: 'bar',
  encoding: {
    x: { field: 'a', type: 'ordinal' },
    y: { field: 'b', type: 'quantitative' },
  },
  data: { name: 'table' }, // note: vega-lite data attribute is a plain object instead of an array
}

const barData = {
  table: [
    { a: 'A', b: 28 },
    { a: 'B', b: 55 },
    { a: 'C', b: 43 },
    { a: 'D', b: 91 },
    { a: 'E', b: 81 },
    { a: 'F', b: 53 },
    { a: 'G', b: 19 },
    { a: 'H', b: 87 },
    { a: 'I', b: 52 },
  ],
}

ReactDOM.render(
  <VegaLite spec={spec} data={barData} />,
  document.getElementById('bar-container')
);

Solution

  • VisualizationSpec type has indeed complex definition, and few properties like mark , encoding.x.type expect specific values.

    // vega-lite\src\type.d.ts
    export declare type StandardType = 'quantitative' | 'ordinal' | 'temporal' | 'nominal';
    
    // vega-lite\src\mark.ts
    export const Mark = {
      arc: 'arc',
      area: 'area',
      bar: 'bar',
      image: 'image',
      line: 'line',
      point: 'point',
      rect: 'rect',
      rule: 'rule',
      text: 'text',
      tick: 'tick',
      trail: 'trail',
      circle: 'circle',
      square: 'square',
      geoshape: 'geoshape'
    } as const;
    

    I copied your code, installed necessary npm packages and saw typescript complained about 2 mentioned things.

    There are many workarounds. const assertion to specific properties, whole object, you can even explicity specify spec is of type VisualizationSpec and it still would be fine.

    You could also import subtypes and use them. Like Mark.bar 'ordinal' as StandardType

    const spec = {
      width: 400,
      height: 200,
      mark: "bar" as const,
      encoding: {
        x: { field: 'a', type: 'ordinal' as const },
        y: { field: 'b', type: 'quantitative' as const },
      },
      data: { name: 'table' },
    }
    
    const spec: VisualizationSpec = {
      width: 400,
      height: 200,
      mark: "bar",
      encoding: {
        x: { field: 'a', type: 'ordinal'},
        y: { field: 'b', type: 'quantitative'},
      },