Amparo
Amparo

Reputation: 824

Parsing json to interface in typescript and check if it is ok

How I can parse a Json string to nested interface type? and check if it is ok?

I have an example but my model is more complex:

export interface User = {
    name: Field;
    surname: Field;
};
export interface Field = { icon: string; text: string; visibility: boolean };
export interface Users = User[]

It would be:

export type user = {
    name: field;
    surname: field;
};
export type field = { icon: string; text: string; visibility: boolean };
export type users = user[]

Or it will be classes. It does not matter.

Here a json example:

[
{
"name": { "text": "David", "icon": "icon1.png", "visibility": true },
"surname": { "text": "Smith", "icon": "icon2.png", "visibility": true }
},
{
"name": { "text": "Arthur", "icon": "icon3.png", "visibility": true },
"surname": { "text": "L.", "icon": "icon6.png", "visibility": true }
},
{
"name": { "text": "Anthony", "icon": "icon1.png", "visibility": false },
"surname": { "text": "Isaacson", "icon": "icon2.png", "visibility": true }
},
{
"name": { "text": "Mike", "icon": "icon3.png", "visibility": true },
"surname": { "text": "Jobs", "icon": "icon5.png", "visibility": false }
}
]

Edit:

here an example why Chithambara's approach is invalid: Playground

Upvotes: 2

Views: 3225

Answers (2)

hlovdal
hlovdal

Reputation: 28180

It seems work but it is very complicated to maintain the code, is there any other way more maintainable?

Yes, you can let typia generate the function. Given the following typescript input:

import typia from "typia";

export interface User {
    name: Field;
    surname: Field;
};
export type Field = { icon: string; text: string; visibility: boolean };

const IsUser = typia.createIs<User>();

const u1 = {
    name: { icon: "i", text: "t", visibility: true },
    surname: { icon: "i", text: "t", visibility: true },
};

const u2 = {
    name: { text: "t", visibility: true },
    surname: { icon: "i", text: "t", visibility: true },
};


if (IsUser(u1)) {
    console.log("u1 is User");
} else {
    console.log("u1 is not User");
}

if (IsUser(u2)) {
    console.log("u2 is User");
} else {
    console.log("u2 is not User");
}

The compiled javascript is

import typia from "typia";
;
const IsUser = input => {
    return "object" === typeof input && null !== input && ("object" === typeof input.name && null !== input.name && ("string" === typeof input.name.icon && "string" === typeof input.name.text && "boolean" === typeof input.name.visibility) && ("object" === typeof input.surname && null !== input.surname && ("string" === typeof input.surname.icon && "string" === typeof input.surname.text && "boolean" === typeof input.surname.visibility)));
};
const u1 = {
    name: { icon: "i", text: "t", visibility: true },
    surname: { icon: "i", text: "t", visibility: true },
};
const u2 = {
    name: { text: "t", visibility: true },
    surname: { icon: "i", text: "t", visibility: true },
};
if (IsUser(u1)) {
    console.log("u1 is User");
}
else {
    console.log("u1 is not User");
}
if (IsUser(u2)) {
    console.log("u2 is User");
}
else {
    console.log("u2 is not User");
}

and the output is

u1 is User
u2 is not User

All the magic is in the IsUser function which you do not have to create yourself.

Upvotes: 1

Alberto Chiesa
Alberto Chiesa

Reputation: 7350

If your validation needs are complex enough, I would evaluate the usage of something like io-ts. It is a library used to automatically generate runtime validations based on metadata in your code.

If your needs are more limited, you can just leverage UserDefined Type Guards.

What a type guard does is take an unknown (or any, there is really no difference inside this kind of function) and tells the compiler that the passed in object is compatible with a certain interface.

export interface Field {
  icon: string;
  text: string;
  visibility: boolean;
}

export interface User {
  name: Field;
  surname: Field;
}

function isField(obj: any): obj is Field {
  return (
    obj != null &&
    typeof obj.icon === "string" &&
    typeof obj.text === "string" &&
    typeof obj.visibility === "boolean"
  );
}

function isUser(obj: any): obj is User {
  return obj != null && isField(obj.name) && isField(obj.surname);

  // you can get fancy and write something like
  // return obj != null && ['name', 'surname'].every(fieldName => isField(obj[fieldName]))
}

// alternative isUser implementation, using a 
// prototype. This will give you a compile error is the
// interface is updated, but not this prototype.
const userProto: User = {
  name: null,
  surname: null
};

function isUserDynamic(obj: any): obj is User {
  return obj != null && Object.keys(userProto).every(fieldName => isField(obj[fieldName]));
}

function validateUserArray(obj: any): obj is User[] {
  if (obj == null) {
    // depending upon the desired approach, you can throw an exception and bail out,
    // or simply return false.
    throw new Error("The array cannot be null");
  }
  if (!Array.isArray(obj)) return false;

  obj.forEach((user, i) => {
    if (!isUser(user))
      throw new Error(
        `Error at index ${i}: ${JSON.stringify(user)} is not a valid user.`
      );
  });

  return true;
}

const json = `[
  {
    "name": { "text": "David", "icon": "icon1.png", "visibility": true },
    "surname": { "text": "Smith", "icon": "icon2.png", "visibility": true }
  },
  {
    "name": { "text": "Arthur", "icon": "icon3.png", "visibility": true },
    "surname": { "text": "L.", "icon": "icon6.png", "visibility": true }
  },
  {
    "name": { "text": "Anthony", "icon": "icon1.png", "visibility": false },
    "surname": { "text": "Isaacson", "icon": "icon2.png", "visibility": true }
  },
  {
    "name": { "text": "Mike", "icon": "icon3.png", "visibility": true },
    "surname": { "text": "Jobs", "icon": "icon5.png", "visibility": false }
  }
]`;

const deserialized: any = JSON.parse(json);

let validatedArray: User[];

if (validateUserArray(deserialized)) {
  // here deserialized is a User[], not an any.
  validatedArray = deserialized;
}

Upvotes: 1

Related Questions