Skip to content

Instantly share code, notes, and snippets.

@Nifled
Last active July 5, 2023 00:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Nifled/6183d4f965831f90a9a2d189164792ff to your computer and use it in GitHub Desktop.
Save Nifled/6183d4f965831f90a9a2d189164792ff to your computer and use it in GitHub Desktop.
Serializing objects with unusual properties (Set, Date, any custom class, etc) to proper JSON-compatible objects.

This is a snippet of code from a project I proposed to my team that was going to automate processes relating to serialization and deserialization of objects sent between our clients (web, desktop, ios, android, macos). All of our web and desktop clients were JS based and each of them included an individual implementation for serializing objects received from other clients. I proposed a library to abstract all of that logic away from the clients and also avoid having to modify code in every client when a change was needed.

This piece of code is related to deserialization of objects.

A lot of our objects (models) inherited from BaseModel and most of these objects made use of data structures or types that weren't compatible with JSON. If we had an object like this:

const object = {
  items: new Set([1, 2, 3, 4, 5]),
  date: new Date(),
  id: new UUID(), // custom internal class
  somNestedObject: {
    items: new Set([6, 7, 8, 9])
  }
};

This object isn't straighforward to JSON.stringify(), since JSON is only compatible with objects and general data types like arrays, numbers, strings and booleans. With the previous code, the stringify() call evaluates to something like:

"
{
  "items": {},
  "date": "2023-03-16T22:52:14.111Z",
  id: {},
  somNestedObject: {
    items: {}
  }
}
"

As you can see, this is not what we wanted at all. So the code based on this gist is what basically automates this for any type of object that inherits BaseModel and it goes through all the the defined properties (with getters) and transform the type to a compatible one (EG items: new Set([1, 2, 3, 4, 5]) -> items: [1, 2, 3, 4, 5]). It pretty much gets it ready for "stringifying".

// ...... other BaseModel code
/**
* Recursively serialize properties through class getters
* Returns ready object for JSON.stringify()
*/
public toJSON(): any {
const proto = Object.getPrototypeOf(this);
const jsonObj = {
[BaseModel.umt]: proto.constructor.universalModelType()
};
// Tterate through whole prototype chain (all the way to Object)
for (let o = this; o !== Object.prototype; o = Object.getPrototypeOf(o)) {
// Iterate through all properties of given object prototype
for (const name of Object.getOwnPropertyNames(o)) {
const descriptor = Object.getOwnPropertyDescriptor(o, name);
if (typeof descriptor!.get === 'function') {
// instead of getting from `o`, get from `this`,
// since `o` is now the prototype of a superclass
const val = (this as any)[name];
const serializedVal = transFormPropertytoJSON(val);
jsonObj[name] = serializedVal;
}
}
}
return jsonObj;
}
// ...... other BaseModel code
/**
* Transform special BaseModel properties (Set, UUID, Date, etc) to JSON-compatible stringified object properties
* @param property object property definition
*/
function transFormPropertytoJSON(property: any): any {
if (property instanceof UUID) {
return property.toString();
} else if (property instanceof Date) {
return property.getTime(); // epoch time
} else if (property instanceof BaseModel) {
// Account for any nested models
return property.toJSON();
} else if (property instanceof Set) {
// There are no Sets in JSON so we need some form of mapping
// Also, call this func recursively on any nested models
return [...property].map(p => transFormPropertytoJSON(p));
}
return property;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment