jgfJsonDecorator.js

const _ = require('lodash');
const deepdash = require('deepdash/deepdash');
deepdash(_);
const check = require('check-types');
const { JgfNode } = require('./jgfNode');
const { JgfEdge } = require('./jgfEdge');
const { JgfGraph } = require('./jgfGraph');
const { JgfMultiGraph } = require('./jgfMultiGraph');

/**
 * Transforms graphs or multi graphs to json or vice versa.
 *
 * Note that this is just called decorator for semantic reasons and does not follow and does not intend to follow
 * the GoF decorator design pattern.
 */
class JgfJsonDecorator {

    /**
     * Creates a Jgf graph or multi graph from JSON.
     * @param {object} json JSON to be transformed to a graph or multigraph. This has to be according to the JGF.
     * @throws Error if json can not be transformed to a graph or multi graph.
     * @returns {JgfGraph|JgfMultiGraph} The created Jgf graph or multi graph object.
     */
    static fromJson(json) {
        if (check.assigned(json.graph)) {
            return this._graphFromJson(json.graph);
        }

        if (check.assigned(json.graphs)) {
            let graph = new JgfMultiGraph(json.type, json.label, json.metadata);
            _.each(json.graphs, (graphJson) => {
                graph.addGraph(this._graphFromJson(graphJson))
            });

            return graph;
        }

        throw new Error('Passed json has to to have a "graph" or "graphs" property.');
    }

    /**
     * Creates a single JGF graph from JSON.
     * @param {object} graphJson
     * @returns {JgfGraph}
     * @private
     */
    static _graphFromJson(graphJson) {
        let graph = new JgfGraph(graphJson.type, graphJson.label, graphJson.directed, graphJson.metadata);

        _.each(graphJson.nodes, (node) => {
            graph.addNode(new JgfNode(node.id, node.label, node.metadata));
        });

        _.each(graphJson.edges, (edge) => {
            graph.addEdge(new JgfEdge(edge.source, edge.target, edge.relation, edge.label, edge.metadata, edge.directed));
        });

        return graph;
    }

    static _guardAgainstInvalidGraphObject(graph) {
        if (!check.instance(graph, JgfGraph) && !check.instance(graph, JgfMultiGraph)) {
            throw new Error('JgfJsonDecorator can only decorate graphs or multi graphs.');
        }
    }

    /**
     * Transforms either a graph or a multi graph object to a JSON representation as per the spec.
     * @param {JgfGraph|JgfMultiGraph} graph The graph to be transformed to JSON.
     * @throws Error if the passed graph or multi graph can not be transformed to JSON.
     * @returns {object} A JSON representation of the passed graph or multi graph as according to the JGF.
     */
    static toJson(graph) {
        this._guardAgainstInvalidGraphObject(graph);

        const isSingleGraph = check.instance(graph, JgfGraph);

        let allGraphsJson = {
            graphs: [],
        };

        this._transformGraphsToJson(graph, allGraphsJson);

        if (isSingleGraph) {
            return this._removeNullValues({ graph: allGraphsJson.graphs[0] });
        }

        allGraphsJson.type = graph.type;
        allGraphsJson.label = graph.label;
        allGraphsJson.metadata = graph.metadata;

        return this._removeNullValues(allGraphsJson);
    }

    static _transformGraphsToJson(graph, allGraphsJson) {
        let normalizedGraph = this._normalizeToMultiGraph(graph);
        _.each(normalizedGraph.graphs, (singleGraph) => {

            let singleGraphJson = {
                type: singleGraph.type,
                label: singleGraph.label,
                directed: singleGraph.directed,
                metadata: singleGraph.metadata,
                nodes: [],
                edges: [],
            };

            this._nodesToJson(singleGraph, singleGraphJson);
            this._edgesToJson(singleGraph, singleGraphJson);

            allGraphsJson.graphs.push(singleGraphJson);
        });
    }

    static _removeNullValues(json) {
        return _.filterDeep(json, (value) => value !== null);
    }

    /**
     * @param {JgfGraph} graph
     * @param {object} json
     * @private
     */
    static _edgesToJson(graph, json) {
        _.each(graph.edges, (edge) => {
            json.edges.push({
                source: edge.source,
                target: edge.target,
                relation: edge.relation,
                label: edge.label,
                metadata: edge.metadata,
                directed: edge.directed,
            });
        });
    }

    /**
     * @param {JgfGraph} graph
     * @param {object} json
     * @private
     */
    static _nodesToJson(graph, json) {
        _.each(graph.nodes, (node) => {
            json.nodes.push({
                id: node.id,
                label: node.label,
                metadata: node.metadata,
            });
        });
    }

    static _normalizeToMultiGraph(graph) {
        let normalizedGraph = graph;
        if (check.instance(graph, JgfGraph)) {
            normalizedGraph = new JgfMultiGraph();
            normalizedGraph.addGraph(graph);
        }

        return normalizedGraph;
    }
}

module.exports = {
    JgfJsonDecorator,
};