npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

json-graphify

v0.6.0

Published

Convert Legacy JSON Objects to JSON Graph

Downloads

18

Readme

Convert Things to Falcor JSON Graph

This makes it easy to bring Falcor into established projects, by transforming existing JSON REST responses into conformant JSON Graph objects.

Note: Experimental! Might change before 1.0.

Install

npm install json-graphify

Example

import graphify from 'json-graphify';

// this will be used to convert user
// objects into JSON Graph fragments
const convertUser = graphify({
  name: 'usersById',
  munge: [{
    select: [ 'avatar' ],
    edit: id => $ref(['mediaById', id])
  }],
  move: [{
    from: [ 'alter_ego' ],
    to: [ 'usersById', '$id' ]
  }]
});

// example of a user object returned
// from a REST API
const user = {
  id: '1',
  username: 'superman',
  avatar: '2',
  alter_ego: { id: '3', username: 'lexluthor' }
};

// convert it to JSON Graph
const jsong = convertUser.toGraph(user);

console.log(user);
// output graph
{
  usersById: {
    1: {
      id: '1',
      username: 'superman',
      avatar: { $type: 'ref', value: [ 'mediaById' , '2' ] },
      alter_ego: { $type: 'ref', value: [ 'usersById' , '3' ] }
    },
    3: {
      id: '3',
      username: 'lexluthor'
    }
  }
}

API Documentation

Main factory function

This module exports a factory function which creates an object transformer based a set of declarative rules you provide. The resultant converter is stateless, and can be kept in memory and endlessly re-used to transform JSON objects into conformant JSON graph fragments. You can create a separate converter for each different type returned from your API.

import graphify from 'json-graphify';

// Create an object that we can use to convert user
// objects returned from the "/api/users/{id}" endpoint
// into JSON graph output.
export default const convertUser = graphify({

  // Objects passed to this converter will
  // live in the top-level "usersById" hash
  // in the returned JSON graph.
  name: 'usersById',

  // Objects passed to this converter will
  // be assumed to have an "id" property.
  // Optional. Defaults to "id".
  idAttribute: 'id',

  // these declarations describe arbitrary operations
  // on incoming JSON objects prior to being transformed
  // into graphs. See "munge objects" below for more
  // details.
  munge: [ ... ],

  // These declarations control the transformation
  // from the existing object structure to JSON graph.
  // See "move objects" below for more details.
  move: [ ... ]
});

Move objects

An array of these are passed as an option to the factory method (see above). A move object has the form { from, to }. from is a path from the root of the input object, which matches zero or more sub-roots in the object tree. to is a path from the root of the output graph, pointing to those subtrees' new home.

{
  // `$index` matches any positive integer, thus
  // if the incoming object has an `avatars` array,
  // this path will match every item in it.
  from: [ 'avatars', '$index' ],

  // `$id` will be replaced by the avatar object's
  // `id` attribute, creating an `mediaById` hash
  // in the output graph.
  to: [ 'mediaById', '$id' ]
}

Munge objects

An array of these are passed as an option to the factory method (see above). A munge object has the form { select, edit }. select is a path from the root of the input object, which matches zero or more sub-roots in the object tree. edit is a function which operates on the found value. Whatever edit returns becomes the new value. If it returns undefined, the value is deleted.

{
  // if the input object has an `avatar` property
  // this selector will match it.
  select: [ 'avatar' ],

  // the value of `inputObject.avatar` is passed to
  // this function, which converts it to a JSON
  // graph reference.
  edit: id => ({ $type: 'ref', value: [ 'mediaById', id ] })
}

convert.toPathValues(...objects)

This converts an input object into an iterator of { path, value } objects. Note: this was recently changed from returning an array to returning an iterable. If an array is needed simply do [ ...convert.toPathValues() ]. Returning an iterator instead of an array allows lazy evaluation. Accepts one or more objects which are converted and added to the result.

const user1 = await fetchJson('/api/users/1');
const user2 = await fetchJson('/api/users/2');
const paths = [ ...convertUser.toPathValues(user1, user2) ];

convert.toGraph(...objects)

This converts an input object into a JSON graph that can be used to populate a Falcor model. Accepts one or more objects which are converted and added to the result.

const user1 = await fetchJson('/api/users/1');
const user2 = await fetchJson('/api/users/2');
const graph = convertUser.toGraph(user1, user2);

graphify.extractor(pathHandlers)

This is an alternative approach, with the goal of being as performant and lightweight as possible. This is more suitable when the objects being converted don't need lots of munging. Subsequently, there's not much heavy object manipulation, and graph-building is more a manual process.

The idea is to create an extractor, which is a factory used to create a pool. The extractor is kept in memory as a singleton and re-used. One pool is created for each Falcor route handler. Insert into the pool to build a graph, then extract to fulfill request paths.

To create an extractor, pass it an array of { path, handler } objects like so:

const extractor = graphify.extractor([{
  path: [ 'users', '$key', 'avatar' ],
  handler: avatar => $ref(['media', avatar.id])
}]);

path is a pattern where $key matches any property and $index matches any positive integer. handler is a function that receives the value and returns a result. For example, in the above, the avatar property on the resource is an ID string; we want to convert it to a JSON graph ref. This conversion is done lazily at extraction time. Next, create a pool and add JSON objects to it like so:

const pool = extractor.start();
for (const user of users) {
  // build up the graph manually
  pool.insert([ 'users', user.id ], user);
}

Next, extract the values you want like so:

const value = pool.extract([ 'users', '123', 'username' ]);

In a falcor router it might look like this:

// allPaths() iterates the pathSet
// passed to the falcor route handler
const pathVals = [];
const pool = extractor.start();
const user = await api.get(`/users/${id}`);
pool.insert([ 'users', user.id ], user);
for (const path of allPaths(pathSet)) {
  const value = pool.extract(path);
  pathVals.push({ path, value });
}
return pathVals;

If the path used to extract matches one of the path patterns provided above, then the returned value is run through the associated handler function.