Reputation: 824
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
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
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