simplifr
v0.2.1
Published
Simplifies JSON into flat single-level path-value structure. Designed for React/Redux apps.
Downloads
66
Maintainers
Readme
simplifr
Simplifies arbitrary JSON data into a flat single-level (path -> description) structure. It's designed for React/Redux/Flux apps. Inspired by Normalizr.
Important NOTE: It's an
experimental
project base on experimental ideas of how we can transform the entire state structure to simplify the way of communicating between components with a complex data store in React apps
Install
npm install --save simplifr
Example App
redux-json-tree - React/Redux app.
Motivation
We can transform the whole Redux store, that can incude complex nested json objects, into a simple flat structure using Simplifr.
So that we can get access to an arbitrary data value in the state by path
identifier 'root.path.to.component.data'
const value = state['root.path.to.component.data']
With the same simplicity we can change this value and create corresponding new state in reducers
// return new state
return Object.assign({}, state, { [action.path]: action.value })
In the case of standard JSON (without transformation), something similar can be written with react-addons-update
addon
// return new state
return update(state, {
path: { to: { component: { data: { $set: action.value }}}}
})
check update for more details.
Simplifr offers a super easy way to communicate between components and Redux store, and to create a complex nested component based on a single store. This idea can be easily extended for multiple reducers with different paths, check redux-simplifr for more details.
Disadvantage of this approach is a possible long path
keys, that may extremely increase the data size.
Possible solution is to encode paths
with a short identifiers. But it may be the work for the future...
How it works
Suppose we have an arbitrary (complex, nested) JSON object
{
level1: {
level2: {
...
levelN: {
key: 'value'
}
}
}
}
We can say that the query path
to the levelN
node is root -> level1 -> level2 -> ... -> levelN
.
Let's encode this path
into a string with dilimiter='.'
'root.level1.level2. ... .levelN'
Also we know that levelN
node is an object with a single child node key
.
Let's define this as description
object
{
type: 'object',
childs: ['key'],
props: 'maybe some props'
}
Notice, that path
contains all necessary meta information about parent nodes, while description
contains all necessary meta information about child nodes.
Based on this meaning, we can express an abitrary JSON data in terms of (path, description)
pairs:
{
path0: description0,
path1: description1,
path2: description2,
...
}
It's a flat single-level structure, that help us to easily get/set the value of any node.
The only thing we need is to know a path
of the node.
Simplifr transform an abitrary JSON into such flat single-level structure.
For example,
{
foo: {
bar: 'buz'
},
qux: [ 1, 2, 3 ]
}
will be simplified to
{
'root': { type: 'object', childs: ['foo', 'qux'] },
'root.foo': { type: 'object', childs: ['bar'] },
'root.foo.bar': 'buz',
'root.qux': { type: 'array', childs: [0, 1, 2] },
'root.qux.0': 1,
'root.qux.1': 2,
'root.qux.2': 3
}
Currently, the leaf nodes have a simple value instead of
description
object.
When it is used
- You work with an arbitrary (complex, nested) JSON data
- You don't know a schema of your data, or node ID's
- You want to create an
editable
React components with Flux/Redux/... . - You want to save some extra states (or props) for any data node.
Is it Normalizr? No. Normalizr requires schemas, object ID's and works primarily with collections of objects.
Simplifr is schema agnostic and works with arbitrary JSON.
Usage with React/Redux
Let's create an editable
react component with JSON data from example above.
Main
import { simplify } from 'simplifr';
Transform the json data
// our source data
const json = {
foo: {
bar: 'buz'
},
qux: [ 1, 2, 3 ]
}
// define simplified data
const simplifiedData = simplify(json)
Then, use it in Redux app as a store data
const store = configureStore(simplifiedData)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
App
Suppose, main App
component has 3 child components: Component
.
The first will edit bar: 'buz'
node. The second will edit the first element in qux
node. And the last will edit the last element in qux
node.
class App extends Component {
...
render(){
return (
<div>
<Component path="root.foo.bar" />
<Component path="root.qux.0" />
<Component path="root.qux.2" />
</div>
)
}
}
Next, let's show how the common Component, Action, Reducer can look like.
Component
class Component extends React.Component {
...
onChange(e){
const { updateAction, path } = this.props
updateAction(path, e.target.value)
}
render(){
const { data } = this.props
return <input value={data} onChange={this.onChange}></input>
}
}
function mapStateToProps(state, props) {
return {
data: state[props.path]
}
}
export default connect(mapStateToProps, actions)(Component)
Actions
function updateAction(path, value){
return {
type: 'UPDATE',
path,
value
}
}
Reducers
import {update} from 'simplifr'
function reducer(state = {}, action){
const { path, value } = action
switch (action.type) {
case 'UPDATE':
return Object.assign({}, state, {[path]: value})
// in general, use simplifr `update` function
// where `value` can take any JSON
//return update(Object.assign({}, state), path, value)
default:
return state
}
}
Result
In the result, 3 input fields will be created. Each of them will change the corresponding node value in our simplified JSON.
API Reference
Let's assume that raw data
is a standard JSON data, eg
{
foo: 'bar'
}
and simplified data
is a flat path-description
data, eg
{
'root': { type: 'object', childs: ['foo'] },
'root.foo': 'bar'
}
Path
string is a string of the following form
'root.path.to.json.node'
or equivalent form with omitting root
'path.to.json.node'
Simplified data
simplify(obj, [dilimiter, root])
Returns simplified data object.
- obj - raw json data to be simplified
- dilimiter - path dilimiter, default
'.'
- root - root path string, default
'root'
.
Example
let obj = {
foo: {
bar: 'buz'
}
}
let data = simplify(obj)
/*
data = {
'root': { type: 'object', childs: ['foo'] },
'root.foo': { type: 'object', childs: ['bar'] },
'root.foo.bar': 'buz'
}
*/
add(data, path, obj, [dilimiter])
Returns simplified data object with updated node
- data - simplified data
- path - a path string to find a node in
data
- obj - json obj to be simplified and added to
path
node indata
- dilimiter - path dilimiter, default
'.'
Example
let data = {
'root': { type: 'object', childs: ['foo'] },
'root.foo': { type: 'object', childs: ['bar'] },
'root.foo.bar': 'buz'
}
add(data, 'root.foo', { array: [1, 2, 3] })
/*
data = {
'root': { type: 'object', childs: ['foo'] },
'root.foo': { type: 'object', childs: ['bar', 'array'] },
'root.foo.bar': 'buz',
'root.foo.array': { type: 'array', childs: [0, 1, 2] },
'root.foo.array.0': 1,
'root.foo.array.1': 2,
'root.foo.array.2': 3
}
*/
update(data, path, obj, [dilimiter])
Returns simplified data object with updated node
- data - simplified data
- path - a path string to find a node in
data
- obj - json obj to be simplified and updated with
path
node indata
- dilimiter - path dilimiter, default
'.'
Example
let data = {
'root': { type: 'object', childs: ['foo'] },
'root.foo': { type: 'object', childs: ['bar'] },
'root.foo.bar': 'buz'
}
update(data, 'root.foo', { array: [1, 2, 3] })
/*
data = {
'root': { type: 'object', childs: ['foo'] },
'root.foo': { type: 'object', childs: ['array'] },
'root.foo.array': { type: 'array', childs: [0, 1, 2] },
'root.foo.array.0': 1,
'root.foo.array.1': 2,
'root.foo.array.2': 3
}
*/
remove(data, path, [dilimiter])
Returns simplified data object with removed node
- data - simplified data
- path - a path string that refer to a node to be removed from
data
- dilimiter - path dilimiter, default
'.'
Example
let data = {
'root': { type: 'object', childs: ['foo'] },
'root.foo': { type: 'object', childs: ['bar', 'array'] },
'root.foo.bar': 'buz',
'root.foo.array': { type: 'array', childs: [0, 1, 2] },
'root.foo.array.0': 1,
'root.foo.array.1': 2,
'root.foo.array.2': 3
}
remove(data, 'root.foo.array')
/*
data = {
'root': { type: 'object', childs: ['foo'] },
'root.foo': { type: 'object', childs: ['bar'] },
'root.foo.bar': 'buz'
}
*/
reset(data, path, [dilimiter])
Returns simplified data object with updated node
- data - simplified data
- path - a path string that refer to a node to be reset in
data
- dilimiter - path dilimiter, default
'.'
Example
let data = {
'root': { type: 'object', childs: ['foo'] },
'root.foo': { type: 'object', childs: ['bar', 'array'] },
'root.foo.bar': 'buz',
'root.foo.array': { type: 'array', childs: [0, 1, 2] },
'root.foo.array.0': 1,
'root.foo.array.1': 2,
'root.foo.array.2': 3
}
reset(data, 'root.foo.array')
/*
data = {
'root': { type: 'object', childs: ['foo'] },
'root.foo': { type: 'object', childs: ['bar', 'array'] },
'root.foo.bar': 'buz',
'root.foo.array': { type: 'array', childs: [] }
}
*/
desimplify(data, [path, dilimiter])
Returns raw json data object.
- data - simplified data
- path - a path string that refer to a node to be desimplified into raw data, default
'root'
. - dilimiter - path dilimiter, default
'.'
Example
let data = {
'root': { type: 'object', childs: ['foo'] },
'root.foo': { type: 'object', childs: ['bar', 'array'] },
'root.foo.bar': 'buz',
'root.foo.array': { type: 'array', childs: [0, 1, 2] },
'root.foo.array.0': 1,
'root.foo.array.1': 2,
'root.foo.array.2': 3
}
let json1 = desimplify(data)
/*
json1 = {
foo: {
bar: 'buz',
array: [1, 2, 3]
}
}
*/
let json2 = desimplify(data, 'root.foo.array')
/*
json2 = [1, 2, 3]
*/
join(args)
Returns path
string with default '.'
dilimiter.
- args - sequence of paths to be joined
Example
const path = join('root', 'path.to', 'component', '', 'data')
// path = 'root.path.to.component.data'
Test
npm test
Licence
MIT