@gnoesiboe/entity-to-object-transformer
    TypeScript icon, indicating that this package has built-in type declarations

    2.1.0 • Public • Published

    Entity to Object transformer

    Transforms entities to native objects and back using a mapping. Enables you to use domain entities in your application, but store them as plain objects, and re-hydrate them when they come back from the database.

    Features

    • Support for custom transformers to transform and reverse transform complex property values like value objects
    • Transforms nested entity relationship including child collections
    • Typescript types included
    • Well tested
    • By default, it throws when you forget or wrongly map a property, to prevent you from making mistakes

    Example usage:

    import EntityToObjectTransformer, { ObjectMapping } from '@gnoesiboe/entity-to-object-transformer'
    
    type AuthorAsObjectType = {
      _id: string;
      name: string;
      createdAt: string;
    };
    
    type BlogItemAsObject = {
      _id: string;
      title: string;
      description: string;
      createdAt: string;
      author: AuthorAsObjectType;
    }
    
    const author = new Author(new Uuid(), 'Peter Pan')
    const blogItem = new BlogItem(new Uuid(), 'Some title', 'Some description', author);
    
    const mapping: ObjectMapping = {
        type: 'object',
        constructor: BlogItem,
        properties: {
            uuid: {
                type: 'property',
                as: '_id',
                transformer: new UuidToStringTransformer(),
            },
            _title: {
                type: 'property',
                as: 'title',
            },
            _description: {
                type: 'property',
                as: 'description',
            },
            createdAt: {
                type: 'property',
                transformer: new DateToStringTransformer(),
            },
            author: {
                type: 'object',
                constructor: Author,
                properties: {
                    uuid: {
                        type: 'property',
                        as: '_id',
                        transformer: new UuidToStringTransformer(),
                    },
                    _name: {
                        as: 'name',
                        type: 'property',
                    },
                    createdAt: {
                        type: 'property',
                        transformer: new DateToStringTransformer(),
                    },
                },
            },
        }
    }
    
    const transformer = new EntityToObjectTransformer<BlogItem, BlogItemAsObject>(mapping);
    
    const blogItemAsObject = transformer.transform(blogItem);
    
    /*
    OUTPUT:
    
    {
        _id: 'ae1a0d1a-2f6b-40e5-90b6-5b3d2f00e83a',
        title: 'Some title',
        description: 'Some description',
        createdAt: '2021-10-14T06:28:37.021Z',
        author: {
            _id: '352cc994-12c0-4e07-b837-0b4e6c31711b',
            name: 'Peter Pan',
            createdAt: '2021-10-14T06:29:00.841Z',
        },
    }
    
     */
    
    const blogItemAsModelAgain = transformer.reverseTransform(blogItemAsObject);
    
    // OUTPUT: Your entity, re-hydrated again

    Documentation

    The EntityToObjectTransformer exposes two methods:

    1. transform → transforms an entity into an object by plucking and transforming the instances properties according to the supplied mapping
    2. reverseTransform → transforms an object into an entity by constructing it, and setting the properties on it, according to the supplied mapping

    The mapping implements two types:

    export type PropertyMapping = {
        type: 'property';
        as?: string;
        transformer?: PropValueTransformer;
    };
    
    export type ObjectMapping = {
        type: 'object';
        constructor: ClassConstructor;
        as?: string;
        properties: {
            [key: string]: PropertyMapping | ObjectMapping;
        };
        ignoredProperties?: string[];
    };

    Starting with an ObjectType, you can nest them together to form a mapping tree:

    Example:

    const mapping: ObjectMapping = {
        type: 'object',
        constructor: BlogItem,
        properties: {
            uuid: {
                type: 'property',
                as: '_id',
                transformer: new UuidToStringTransformer(),
            },
            _title: {
                type: 'property',
                as: 'title',
            },
            _description: {
                type: 'property',
                as: 'description',
            },
            createdAt: {
                type: 'property',
                transformer: new DateToStringTransformer(),
            },
            author: {
                type: 'object',
                constructor: Author,
                properties: {
                    uuid: {
                        type: 'property',
                        as: '_id',
                        transformer: new UuidToStringTransformer(),
                    },
                    _name: {
                        as: 'name',
                        type: 'property',
                    },
                    createdAt: {
                        type: 'property',
                        transformer: new DateToStringTransformer(),
                    },
                },
            },
        }
    }

    ObjectType

    Represents an instance transformed into an object or an array of instances transformed into an array of objects.

    key type description
    type "object" This should always be "object" and should be provided at all times to make distinction between object-types and property-types
    constructor ClassConstructor A class constructor to use for reverse transforming objects into entity instances
    as string | undefined When defining a child entity in the mapping, this can be used to specify the name of the key on the parent object that it is transformed into
    properties Record<string, PropertyMapping | ObjectMapping> An object containing as key the name of the property on the instance, and as value ObjectType or PropertyType mappings
    ignoredProperties string[] By default the transformer will throw an Error when you forget to map properties, to prevent any mistakes. When you want a instance property not to be mapped, add the property key in this array.

    PropertyType

    Represents a property transformed into something else (up to ypu), or an array of properties transformed.

    key type description
    type "property" This should always be "property" and should be provided at all times to make distinction between object-types and property-types
    as string | undefined If you don't want the original property name to be outputted in the object, define an alternate name
    transformer PropValueTransformer A class instance implementing PropValueTransformer interface that can be called during the transformations to change the property value into anything you want

    Recipes

    Child collections with mixed instance types

    When transforming a child collection of mixed instance types, for instance a child collection with instances of Features and Colors, don't define it as an object-mapping, but as a property-mapping and write a custom transformer. Like:

    export default class ProductAttributeToObjectTransformer
        implements PropValueTransformer<Color | Feature, ColorAsObject | FeatureAsObject>
    {
        reverseTransform(to: ColorAsObject | FeatureAsObject): Color | Feature {
            // ...
        }
    
        transform(from: Color | Feature): ColorAsObject | FeatureAsObject {
            // ...
        }
    }

    Feel free however to re-use the EntityToObjectTransformer (or any other custom transformer you wrote) inside this transformer, to transform any child objects of some sorts.

    Instantiating classes with runtime validation

    As the EntityToObjectTranformer will, when reverse transforming, instantiate objects without supplying constructor variables, and there might be some runtime validation there, this might result in errors. To prevent this, for now, you need to write a custom transformer for this and supply this in your mapping.

    Known limitations and todos

    • As constructors are instantiated without arguments, and the props are set on the instance, there might be problems with runtime validation in the constructor. It would be nice if we could support this without the user having to write a custom transformer for it.
    • Support arrays that contain different types of instances, without using a custom transformer
    • Be able to use custom child collection classes and loop through them

    Install

    npm i @gnoesiboe/entity-to-object-transformer

    DownloadsWeekly Downloads

    12

    Version

    2.1.0

    License

    MIT

    Unpacked Size

    28 kB

    Total Files

    27

    Last publish

    Collaborators

    • gnoesiboe