DEV Community

Decoding JSON with Typescript

Joan Llenas Masó on December 23, 2018

Typescript is very well suited for adding type safety to a JavaScript program, but on its own, it's not enough to guarantee that it won't crash at ...
Collapse
 
0x80 profile image
Thijs Koerselman • Edited

This seems very similar to using a runtime schema validation library like Joi.

I've been using this helper function to typecheck API payloads. It returns the payload cast to the type it is supposed to be when the schema validates. If it doesn't validate the function throws an error with messages explaining what part of the schema failed to comply.

import * as Joi from "joi";

export function validatePayload<T>(payload: T, schema: Joi.AnySchema): T {
  const { error, value } = Joi.validate(payload, schema);

  if (error) {
    /**
     * Collect all of the Joi error messages and combine them in one
     * comma-separated string.
     */
    const errorMessages = error.details.map(errorItem => errorItem.message);
    throw new Error(errorMessages.join(","));
  }

  return value;
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
joanllenas profile image
Joan Llenas Masó

Never used Joi before but it looks much more powerful (and heavy).
This library is just a few bytes, although it has all the basic pieces you need to build more Joi-ish stuff (I guess).
Something you can do with ts.data.json that I don't see in the Joi docs is mapping over a decoded value. For instance:

type User = {
    id: number,
    name: string,
    dateOfBirth: Date
}

JsonDecoder.object<User>(
    {
        id: JsonDecoder.number,
        name: JsonDecoder.string,
        dateOfBirth: JsonDecoder.string.map(stringDate => new Date(stringDate)),
    },
    'User'
);
Enter fullscreen mode Exit fullscreen mode

Very useful.

Thanks for pointing that out Thijs.
Cheers!

Collapse
 
0x80 profile image
Thijs Koerselman

Mapping seems useful indeed :)

I use Joi on the server so size is not an issue there. For the browser I use yup. Similar API but much smaller footprint.

Thread Thread
 
joanllenas profile image
Joan Llenas Masó

Interesting! smaller footprint but powerful too.
I might add an alternatives section in the ts.data.json docs.
Thanks!

Collapse
 
hmendezm profile image
H. Mendez

HI everybody.

I am using the JsonDecoder and I have two issues that I do not know how to solv.

  1. how I represent any type
  2. I have class functions that are public and JsonDecoder asks me to add them. How should I handle this situation?

(alias) namespace JsonDecoder
import JsonDecoder
Argument of type '{ optionID: Decoder; name: Decoder; study: any; workflowId: Decoder; workflow: Decoder; scenarios: Decoder; disabled: Decoder; defaultTask: Decoder<...>; }' is not assignable to parameter of type 'DecoderObject'.
Type '{ optionID: Decoder; name: Decoder; study: any; workflowId: Decoder; workflow: Decoder; scenarios: Decoder; disabled: Decoder; defaultTask: Decoder<...>; }' is missing the following properties from type 'DecoderObject': getTaskByID, getScenarioByID, addScenario, updateScenario, getPreviousTaskts
Best,
Hmendezm

Collapse
 
joanllenas profile image
Joan Llenas Masó

You can decode the methods with JsonDecoder.succeed:

...
getTaskByID: JsonDecoder.succeed
...

but the class will lose all the instance information during the decoding process. If you are using the instanceof operator later on it won't work. Aside from that, you are good to go.

Collapse
 
hmendezm profile image
H. Mendez

Hey Joan, sorry for bothering you again.

I have a case where the columns from the JSON can be with different for instance the JSON can have _color: yellow or color: yellow.

I am using the keyMap for the _color and I thought that if the column does not exist will take color but it is not the case. How can I have more the one keymap option?

Best
Hmendezm

Collapse
 
hmendezm profile image
H. Mendez

Hi Joan, sorry for bothering you. U have a case when the property in the class can be a number or null. How I can set a default value when it is null?

Best
Hmendezm

Collapse
 
hmendezm profile image
H. Mendez

Thanks, Joan for the help.

Collapse
 
blocka profile image
Avi Block

Have you seen io-ts? io-ts also removes the duplication by creating a "decoder" and a type at the same time.

interface User {
  firstName: string;
  lastName: string;
  age: string;
}

const userDecoder = JsonDecoder.object<User>(
  {
    firstName: JsonDecoder.string,
    lastName: JsonDecoder.string,
    age: JsonDecoder.number
  },
  'User'
)

what would happen in this case?

Collapse
 
joanllenas profile image
Joan Llenas Masó • Edited

I'm aware of io-ts but I haven't jumped on the fp-ts bandwagon yet. The learning curve seems quite steep.

The decoder you just mentioned would fail at compile-time, because number and string are different types.
If the data you want to decode is a number but you want a string you could do:

const userDecoder = JsonDecoder.object<User>(
  {
    firstName: JsonDecoder.string,
    lastName: JsonDecoder.string,
    age: JsonDecoder.number.map(num => num.toString())
  },
  'User'
)

Cheers!

Collapse
 
kofno profile image
Ryan L. Bell

I had a similar idea.

github.com/kofno/jsonous

Collapse
 
joanllenas profile image
Joan Llenas Masó

Looks very nice 👌
Very Elmish, which I like!
It's in the related libraries section now

Collapse
 
kofno profile image
Ryan L. Bell

Awesome! Thank you!

Collapse
 
rutvikdeshmukh profile image
Rutvik Deshmukh

actually one thing i observed while testing this package, userDecoder.decodeToPromise is the method name for getting javascript object from specified JSON object, but in your article you have written as decodePromise, this method not exist on useDecoder

Collapse
 
hmendezm profile image
H. Mendez

Hi Joan, sorry for bothering you. U have a case when the property in the class can be a number or null. How I can set a default value when it is null?

Best
Hmendezm

Collapse
 
joanllenas profile image
Joan Llenas Masó

Hi! You can use JsonDecoder.failover: github.com/joanllenas/ts.data.json...
There are other more exotic options but I think this is enough in most cases.

Collapse
 
hmendezm profile image
H. Mendez

Thanks Joan.
I did the failover and it is working as intending. I got problems with the JsonDecoder.Success in functions as you recommended posts back (Sep 27).

Uncaught (in promise): TypeError: Cannot read property 'toApiDTO' of undefined.

public static cvt= JsonDecoder.object<CoordinateValueType>({
    latitude: JsonDecoder.failover(0, JsonDecoder.number),
    longitude: JsonDecoder.failover(0, JsonDecoder.number),
    altitude: JsonDecoder.failover(0, JsonDecoder.number),
    equals: JsonDecoder.succeed,
    toApiDTO: JsonDecoder.succeed,
    toString: JsonDecoder.succeed
}, 'CoordinateValueType', {
    latitude: '_latitude',
    longitude: '_longitude',
    altitude: '_altitude',
    equals: 'equals',
    toApiDTO: 'toApiDTO',
    toString: 'toString'
})

Best
Hmendezm

Collapse
 
dgcp3 profile image
DGCP3

Zod enters the room