Search code examples
node.jstypescriptinterfacebuffer

Discriminate type of slice of Node.js Buffer in TypeScript


I have defined these interfaces in typescript:

enum TypeId {
  A = 0
  B = 1
  C = 2
}

interface A {
  id: TypeId.A
}

interface B {
  id: TypeId.B
  foo: string
}

interface C {
  id: TypeId.C
}

type MyType = A | B | C

Now I want to unserialize these types from a Buffer instance. I read one byte to get the ID and if it matches TypeId.B, 4 bytes have to be read for the value of foo.

How can I do this in a function with the signature parse(buffer: Buffer): MyType?

Thanks for any help.


Solution

  • I infer that the Buffer to which you refer is the one from Node.js.

    This is how you can parse binary data in Node: (specifically, in your case) an integer, optionally followed by a UTF-8 string:

    ./src/index.ts:

    import {TextDecoder, TextEncoder} from 'util';
    
    enum Id {
      A = 0,
      B = 1,
      C = 2,
    }
    
    interface A {
      id: Id.A;
    }
    
    interface B {
      id: Id.B;
      foo: string
    }
    
    interface C {
      id: Id.C;
    }
    
    type MyType = A | B | C;
    
    export function parse (buf: Buffer): MyType {
      const id = buf.readUInt8(0);
      if (![Id.A, Id.B, Id.C].includes(id)) throw new Error('Invalid ID');
      if (id === Id.B) {
        // equivalent:
        // const foo = buf.slice(1, 5).toString('utf-8');
        const foo = new TextDecoder().decode(buf.slice(1, 5));
        return {id, foo};
      }
      return {id};
    }
    
    function main () {
      // equivalent:
      // const buf0 = Buffer.from([0, ...Buffer.from('hello', 'utf-8')]);
      const encoder = new TextEncoder();
      const buf0 = Buffer.from([0, ...encoder.encode('hello')]);
      const buf1 = Buffer.from([1, ...encoder.encode('example string')]);
      const buf2 = Buffer.from([2, ...encoder.encode('another one')]);
    
      console.log(parse(buf0)); // { id: 0 }
      console.log(parse(buf1)); // { id: 1, foo: 'exam' }
      console.log(parse(buf2)); // { id: 2 }
    }
    
    main();
    

    For reference, this is the TSConfig I used:

    {
      "compilerOptions": {
        "exactOptionalPropertyTypes": true,
        "isolatedModules": true,
        "lib": [
          "ESNext"
        ],
        "module": "ESNext",
        "moduleResolution": "Node",
        "noUncheckedIndexedAccess": true,
        "outDir": "dist",
        "strict": true,
        "target": "ESNext",
      },
      "include": [
        "./src/**/*"
      ]
    }