2

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

2 Answers 2

1

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;
}
Sign up to request clarification or add additional context in comments.

4 Comments

It seems work but it is very complicated to maintain the code, is there any other way more maintainable?
If your types are "regular" (i.e. the user interface has 20 fields, all of them of type Field), you could make User a class, instantiate a prototype with const proto = new User() and use Object.keys(proto) to get a list of the fields to be checked, via return obj != null && Object.keys(proto).every(fieldName => isField(obj[fieldName])) in isUser.
Or, use io-ts. More maintainable, for sure, but maybe not so simple. You could also build a code generator, to build you isUser function, and run it every time the User interface changes.
I Added a isUserDynamic function, alternative to isUser to show how it could be done, keeping User as an interface.
1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.