
Security News
vlt Launches "reproduce": A New Tool Challenging the Limits of Package Provenance
vlt's new "reproduce" tool verifies npm packages against their source code, outperforming traditional provenance adoption in the JavaScript ecosystem.
Serialize and deserialize complex object graphs to JSON
Serializr is a utility library that helps converting json structures into complex object graphs and the other way around.
Features:
Non-features:
npm install serializr --save
import {
createModelSchema, primitive, reference, list, object, identifier, serialize, deserialize
} from "serializr";
// Example model classes
class User {
uuid = Math.random();
displayName = "John Doe";
}
class Message {
message = "Test";
author = null;
comments = [];
}
findUserById(uuid, callback) {
callback(null, fetchUserSomewhere(uuid))
}
// Create model schemas
createModelSchema(Message, {
message: primitive(),
author: reference(User, findUserById),
comments: list(object(Message))
})
createModelSchema(User, {
uuid: identifier(),
displayName: primitive()
})
// can now deserialize and serialize!
const message = deserialize(Message, {
message: "Hello world",
author: 17,
comments: [{
message: "Welcome!",
author: 23
}]
})
const json = serialize(message)
With decorators (TypeScript or ESNext) building model schemas is even more trivial:
import {
createModelSchema, primitive, reference, list, object, identifier, serialize, deserialize,
serializable
} from "serializr";
class User {
@serializable(identifier())
uuid = Math.random();
@serializable
displayName = "John Doe";
}
class Message {
@serializable
message = "Test";
@serializable(reference(User, findUserById))
author = null;
@serializable(list(object(Message)))
comments = [];
}
TypeScript
Enable the compiler option experimentalDecorators
in tsconfig.json
or pass it as flag --experimentalDecorators
to the compiler.
Babel:
Install support for decorators: npm i --save-dev babel-plugin-transform-decorators-legacy
. And enable it in your .babelrc
file:
{
"presets": ["es2015", "stage-1"],
"plugins": ["transform-decorators-legacy"]
}
Probably you have more plugins and presets in your .babelrc
already, note that the order is important and transform-decorators-legacy
should come as first.
The two most important functions exposed by serializr are serialize(modelschema?, object) -> json tree
and deserialize(modelschema, json tree) -> object graph
.
What are those model schemas?
The driving concept behind (de)serialization is a ModelSchema. It describes how model object instances can be (de)serialize to json.
A model schema simple looks like this:
const todoSchema = {
factory: (context) => new Todo(),
extends: ModelSchema,
props: {
modelfield: PropSchema
}
}
The factory
tells how to construct new instances during deserialization.
The optional extends
property denotes that this model schema inherits it's props from another model schema.
The props section describe how individual model properties are to be (de)serialized. Their names match the model field names.
The combination fieldname: true
is simply a shorthand for fieldname: primitive()
For convenience, model schemas can be stored on the constructor function of a class. This allows you to pass in a class reference everywhere where a model schema is required. See the examples below.
Prop schemas contain the strategy on how individual fields should be serialized. It denotes whether a field is a primitive, list, whether it needs to be aliased, refers to other model objects etc. Propschemas are composable. See the API section below for the details, but these are the built in property schemas:
primitive()
: Serialize a field as primitive valueidentifier()
: Serialize a field as primitive value, use it as identifier when serializing references (see ref
)date()
: Serializes dates (as epoch number)alias(name, propSchema)
: Serializes a field under a different namelist(propSchema)
: Serializes an array based collectionmap(propSchema)
: Serializes an Map or string key based collectionobject(modelSchema)
: Serializes an child model elementreference(modelSchema, lookupFunction?)
: Serializes a reference to another model elementcustom(serializeFunction, deserializeFunction)
: Create your own property serializer by providing two functions, one that converts modelValue to jsonValue, and one that does the inverse"*": true
that serializes all enumerable, non mentioned values as primitiveIt is possible to define your own prop schemas. You can define your own propSchema by creating a function that returns an object with the following signature:
{
serializer: (sourcePropertyValue: any) => jsonValue,
deserializer: (jsonValue: any, callback: (err, targetPropertyValue: any) => void, context?, currentPropertyValue?) => void
}
For inspiration, take a look at the source code of the existing ones on how they work, it is pretty straight forward.
The context object is an advanced feature and can be used to obtain additional context related information about the deserialization process.
context
is available as:
ref
prop schema's (see below)deserializer
of a custom propSchemaWhen deserializing a model elememt / property, the following fields are available on the context object:
json
: Returns the complete current json object that is being deserializedtarget
: The object currently being deserialized. This is the object that is returned from the factory function.parentContext
: Returns the parent context of the current context. For example if a child element is being deserialized, the context.target
refers to the current model object, and context.parentContext.target
refers to the parent model object that owns the current model object.args
: If custom arguments were passed to the deserialize
/ update
function, they are available as context.args
.Creates a model schema that (de)serializes from / to plain javascript objects.
It's factory method is: () => ({})
Parameters
props
object property mapping,Examples
var todoSchema = createSimpleSchema({
title: true,
done: true
};
var json = serialize(todoSchema, { title: "Test", done: false })
var todo = deserialize(todoSchema, json)
Returns object model schema
Creates a model schema that (de)serializes an object created by a constructor function (class).
The created model schema is associated by the targeted type as default model schema, see setDefaultModelSchema.
It's factory method is () => new clazz()
(unless overriden, see third arg).
Parameters
clazz
function clazz or constructor functionprops
object property mappingfactory
function optional custom factory. Receives context as first argExamples
function Todo(title, done) {
this.title = title;
this.done = done;
}
createModelSchema(Todo, {
title: true,
done: true
})
var json = serialize(new Todo("Test", false))
var todo = deserialize(Todo, json)
Returns object model schema
Decorator that defines a new property mapping on the default model schema for the class it is used in.
Parameters
arg1
arg2
arg3
Examples
class Todo {
Returns PropertyDescriptor
Returns the standard model schema associated with a class / constructor function
Parameters
clazz
function class or constructor functionthing
Returns object model schema
Sets the default model schema for class / constructor function.
Everywhere where a model schema is required as argument, this class / constructor function
can be passed in as well (for example when using child
or ref
.
When passing an instance of this class to serialize
, it is not required to pass the model schema
as first argument anymore, because the default schema will be inferred from the instance type.
Parameters
clazz
function class or constructor functionmodelSchema
Returns object model schema
Serializes an object (graph) into json using the provided model schema. The model schema can be omitted if the object type has a default model schema associated with it. If a list of objects is provided, they should have an uniform type.
Parameters
arg1
modelschema to use. Optionalarg2
object(s) to serializeReturns object serialized representation of the object
Deserializes an json structor into an object graph. This process might be asynchronous (for example if there are references with an asynchronous lookup function). The function returns an object (or array of objects), but the returned object might be incomplete until the callback has fired as well (which might happen immediately)
Parameters
schema
json
json data to deserializecallback
function node style callback that is invoked once the deserializaiton has finished.
First argument is the optional error, second argument is the deserialized object (same as the return value)customArgs
any custom arguments that are available as context.args
during the deserialization process. This can be used as dependency injection mechanism to pass in, for example, stores.Similar to deserialize, but updates an existing object instance. Properties will always updated entirely, but properties not present in the json will be kept as is. Further this method behaves similar to deserialize.
Parameters
modelSchema
object , optional if it can be inferred from the instance typetarget
object target instance to updatejson
object the json to deserializecallback
function the callback to invoke once deserialization has completed.customArgs
any custom arguments that are available as context.args
during the deserialization process. This can be used as dependency injection mechanism to pass in, for example, stores.Indicates that this field contains a primitive value (or Date) which should be serialized literally to json.
Examples
createModelSchema(Todo, {
title: primitive()
})
console.dir(serialize(new Todo("test")))
// outputs: { title : "test" }
Returns PropSchema
Similar to primitive, but this field will be marked as the identifier for the given Model type.
This is used by for example ref()
to serialize the reference
Identifier accepts an optional registerFn
with the signature:
(id, target, context) => void
that can be used to register this object in some store. note that not all fields of this object might have been deserialized yet
Parameters
registerFn
function optional function to register this object during creation.Examples
var todos = {};
var s = _.createSimpleSchema({
id: _.identifier((id, object) => todos[id] = object),
title: true
})
_.deserialize(s, {
id: 1, title: "test0"
})
_.deserialize(s, [
{ id: 2, title: "test2" },
{ id: 1, title: "test1" }
])
t.deepEqual(todos, {
1: { id: 1, title: "test1" },
2: { id: 2, title: "test2" }
})
Returns PropSchema
Similar to primitive, serializes instances of Date objects
Alias indicates that this model property should be named differently in the generated json. Alias should be the outermost propschema.
Parameters
alias
string name of the json field to be used for this propertyname
propSchema
PropSchema propSchema to (de)serialize the contents of this fieldExamples
createModelSchema(Todo, {
title: alias("task", primitive())
})
console.dir(serialize(new Todo("test")))
// { task : "test" }
Returns PropSchema
Can be used to create simple custom propSchema.
Parameters
serializer
function function that takes a model value and turns it into a json valuedeserializer
function function that takes a json value and turns it into a model valueExamples
var schema = _.createSimpleSchema({
a: _.custom(
function(v) { return v + 2 },
function(v) { return v - 2 }
)
})
t.deepEqual(_.serialize(s, { a: 4 }), { a: 6 })
t.deepEqual(_.deserialize(s, { a: 6 }), { a: 4 })
Returns propSchema
object
indicates that this property contains an object that needs to be (de)serialized
using it's own model schema.
N.B. mind issues with circular dependencies when importing model schema's from other files! The module resolve algorithm might expose classes before createModelSchema
is executed for the target class.
Parameters
modelSchema
modelSchema to be used to (de)serialize the childExamples
createModelSchema(SubTask, {
title: true
})
createModelSchema(Todo, {
title: true
subTask: object(SubTask)
})
const todo = deserialize(Todo, {
title: "Task",
subTask: {
title: "Sub task"
}
})
Returns PropSchema
reference
can be used to (de)serialize references that points to other models.
The first parameter should be either a ModelSchema that has an identifier()
property (see identifier)
or a string that represents which attribute in the target object represents the identifier of the object.
The second parameter is a lookup function that is invoked during deserialization to resolve an identifier to an object. It's signature should be as follows:
lookupFunction(identifier, callback, context)
where:
identifier
is the identifier being resolvedcallback
is a node style calblack function to be invoked with the found object (as second arg) or an error (first arg)context
see context.The lookupFunction is optional. If it is not provided, it will try to find an object of the expected type and required identifier within the same JSON document
N.B. mind issues with circular dependencies when importing model schema's from other files! The module resolve algorithm might expose classes before createModelSchema
is executed for the target class.
Parameters
target
: ModelSchema or stringlookup
function functionlookupFn
Examples
createModelSchema(User, {
uuid: identifier(),
displayname: primitive()
})
createModelSchema(Post, {
author: reference(User, findUserById)
message: primitive()
})
function findUserById(uuid, callback) {
fetch("http://host/user/" + uuid)
.then((userData) => {
deserialize(User, userData, callback)
})
.catch(callback)
}
deserialize(
Post,
{
message: "Hello World",
author: 234
},
(err, post) => {
console.log(post)
}
)
Returns PropSchema
List indicates that this property contains a list of things. Accepts a sub model schema to serialize the contents
Parameters
propSchema
PropSchema to be used to (de)serialize the contents of the arrayExamples
createModelSchema(SubTask, {
title: true
})
createModelSchema(Todo, {
title: true
subTask: list(child(SubTask))
})
const todo = deserialize(Todo, {
title: "Task",
subTask: [{
title: "Sub task 1"
}]
})
Returns PropSchema
Similar to list, but map represents a string keyed dynamic collection. This can be both plain objects (default) or ES6 Map like structures. This will be inferred from the initial value of the targetted attribute.
Parameters
propSchema
anyconst todoSchema = {
factory: () => {},
props: {
task: primitive(),
owner: reference("_userId", UserStore.findUserById) // attribute of the owner attribute of a todo + lookup function
subTasks: alias("children", list(object(todoSchema)))
}
}
const todo = deserialize(todoSchema,
{ task: "grab coffee", owner: 17, children: [] },
(err, todo) => { console.log("finished loading todos") }
);
const todoJson = serialize(todoSchema, todo)
function Todo(parentTodo) {
this.parent = parentTodo; // available in subTasks
}
const todoSchema = {
factory: (context) => new Todo(context.parent),
props: {
task: primitive(),
owner: reference("_userId", UserStore.findUserById) // attribute of the owner attribute of a todo + lookup function
subTasks: alias("children", list(object(todoSchema)))
}
}
setDefaultModelSchema(Todo, todoSchema)
const todo = deserialize(Todo, // just pass the constructor name, schema will be picked up
{ task: "grab coffee", owner: 17, children: [] },
(err, todos) => { console.log("finished loading todos") }
);
const todoJson = serialize(todo) // no need to pass schema explicitly
function Todo() {
}
// creates a default factory, () => new Todo(), stores the schema as default model schema
createModelSchema(Todo, {
task: primitive()
})
const todo = deserialize(Todo, // just pass the constructor name, schema will be picked up
{ task: "grab coffee", owner: 17, children: [] },
(err, todos) => { console.log("finished loading todos") }
);
const todoJson = serialize(todo) // no need to pass schema explicitly
class Todo {
@serializable(primitive())
task = "Grab coffee";
@serializable(reference("_userId", UserStore.findUserById))
owner = null;
@serializable(alias("children", list(object(todoSchema)))
subTasks = [];
}
// note that (de)serialize also accepts lists
const todos = deserialize(Todo,
[{
task: "grab coffee", owner: 17, children: []
}],
(err, todos) => { console.log("finished loading todos") }
);
const todoJson = serialize(todos)
const someTodoStoreById = {}
getDefaultModelSchema(Todo).factory = (context, json) => {
if (someTodoStoreById[json.id])
return someTodoStoreById[json.id] // reuse instance
return someTodoStoreById[json.id] = new Todo()
};
This pattern is useful to avoid singletons but allow to pass context specific data to constructors. This can be done by passing custom data to deserialize
/ update
as last argument,
which will be available as context.args
on all places where context is available:
class User {
constructor(someStore) {
// User needs access to someStore, for whatever reason
}
}
// create model schema with custom factory
createModelSchema(User, { username: true }, context => {
return new User(context.args.someStore)
})
// don't want singletons!
const someStore = new SomeStore()
// provide somestore through context of the deserialization process
const user = deserialize(
User,
someJson,
(err, user) => { console.log("done") },
{
someStore: someStore
}
)
// Box.js:
import {observable, computed} from 'mobx';
import {randomUuid} from '../utils';
import {serializable, identifier} from 'serializr';
export default class Box {
@serializable(identifier()) id;
@observable @serializable name = 'Box' + this.id;
@serializable @observable x = 0;
@serializable @observable y = 0;
@computed get width() {
return this.name.length * 15;
}
}
// Store.js:
import {observable, transaction} from 'mobx';
import {createSimpleSchema, ref, identifier, child, list, serialize, deserialize, update} from 'serializr';
import Box from './box';
// The store that holds our domain: boxes and arrows
const store = observable({
boxes: [],
arrows: [],
selection: null
});
// Model of an arrow
const arrowModel = createSimpleSchema({
id: identifier(),
from: reference(Box)
to: reference(Box)
})
// Model of the store itself
const storeModel = createSimpleSchema({
boxes: list(object(Box)),
arrows: list(object(arrowModel)),
// context.target is the current store being deserialized
selection: ref(Box)
})
// example data
store.boxes.push(
new Box('Rotterdam', 100, 100),
new Box('Vienna', 650, 300)
);
store.arrows.push({
id: randomUuid(),
from: store.boxes[0],
to: store.boxes[1]
});
// (de) serialize functions
function serializeState(store) {
return serialize(storeModel, store);
}
function deserializeState = (store, json) {
transaction(() => {
update(storeModel, store, json);
})
}
"*": true
respect extends clauses1.1.1
Which should have been called 1.0...:-)
lookupFunction
of ref
is now optional, if it is not provided, serializr will try to resolve the reference within the current document. Types are respected while resolvingref
has been renamed to reference
child
has been renamed to object
false
is now also an acceptable value for propSchema's"*": true
now has the special meaning that all enumerable, primitive fields will be serialized. Will throw on non-primitive fieldscustom(serializer, deserializer)
identifier
now supports an optional callback that can be used to register new instances in some storeInitial release
FAQs
Serialize and deserialize complex object graphs to JSON
The npm package serializr receives a total of 18,930 weekly downloads. As such, serializr popularity was classified as popular.
We found that serializr demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 5 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
vlt's new "reproduce" tool verifies npm packages against their source code, outperforming traditional provenance adoption in the JavaScript ecosystem.
Research
Security News
Socket researchers uncovered a malicious PyPI package exploiting Deezer’s API to enable coordinated music piracy through API abuse and C2 server control.
Research
The Socket Research Team discovered a malicious npm package, '@ton-wallet/create', stealing cryptocurrency wallet keys from developers and users in the TON ecosystem.