├── .babelrc ├── .gitignore ├── .npmignore ├── .nyc_output ├── 8542a855d8c2bbbca6f6161afeb8ae67.json └── c33e48d54aeb1dcf22bb746853cabc7f.json ├── README.md ├── index.js ├── package.json └── test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env", 4 | "stage-0", 5 | "react" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist.js 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test.js 2 | -------------------------------------------------------------------------------- /.nyc_output/8542a855d8c2bbbca6f6161afeb8ae67.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /.nyc_output/c33e48d54aeb1dcf22bb746853cabc7f.json: -------------------------------------------------------------------------------- 1 | {"/Users/jxnblk/repos/json-react/index.js":{"path":"/Users/jxnblk/repos/json-react/index.js","statementMap":{"0":{"start":{"line":3,"column":0},"end":{"line":5,"column":3}},"1":{"start":{"line":6,"column":0},"end":{"line":6,"column":82}},"2":{"start":{"line":8,"column":13},"end":{"line":8,"column":29}},"3":{"start":{"line":10,"column":14},"end":{"line":10,"column":44}},"4":{"start":{"line":12,"column":39},"end":{"line":12,"column":93}},"5":{"start":{"line":14,"column":16},"end":{"line":25,"column":1}},"6":{"start":{"line":15,"column":14},"end":{"line":15,"column":84}},"7":{"start":{"line":17,"column":2},"end":{"line":17,"column":42}},"8":{"start":{"line":17,"column":31},"end":{"line":17,"column":42}},"9":{"start":{"line":18,"column":17},"end":{"line":20,"column":19}},"10":{"start":{"line":19,"column":4},"end":{"line":19,"column":35}},"11":{"start":{"line":21,"column":13},"end":{"line":21,"column":40}},"12":{"start":{"line":22,"column":14},"end":{"line":22,"column":31}},"13":{"start":{"line":23,"column":11},"end":{"line":23,"column":59}},"14":{"start":{"line":24,"column":2},"end":{"line":24,"column":12}},"15":{"start":{"line":27,"column":14},"end":{"line":29,"column":1}},"16":{"start":{"line":28,"column":2},"end":{"line":28,"column":90}},"17":{"start":{"line":31,"column":11},"end":{"line":38,"column":1}},"18":{"start":{"line":32,"column":13},"end":{"line":32,"column":15}},"19":{"start":{"line":33,"column":2},"end":{"line":36,"column":3}},"20":{"start":{"line":34,"column":4},"end":{"line":34,"column":37}},"21":{"start":{"line":34,"column":28},"end":{"line":34,"column":37}},"22":{"start":{"line":35,"column":4},"end":{"line":35,"column":25}},"23":{"start":{"line":37,"column":2},"end":{"line":37,"column":14}},"24":{"start":{"line":40,"column":15},"end":{"line":60,"column":1}},"25":{"start":{"line":41,"column":2},"end":{"line":41,"column":40}},"26":{"start":{"line":41,"column":30},"end":{"line":41,"column":40}},"27":{"start":{"line":42,"column":13},"end":{"line":42,"column":29}},"28":{"start":{"line":43,"column":14},"end":{"line":43,"column":22}},"29":{"start":{"line":45,"column":2},"end":{"line":50,"column":3}},"30":{"start":{"line":46,"column":4},"end":{"line":49,"column":6}},"31":{"start":{"line":51,"column":17},"end":{"line":53,"column":4}},"32":{"start":{"line":52,"column":4},"end":{"line":52,"column":27}},"33":{"start":{"line":55,"column":2},"end":{"line":59,"column":4}}},"fnMap":{"0":{"name":"_interopRequireDefault","decl":{"start":{"line":12,"column":9},"end":{"line":12,"column":31}},"loc":{"start":{"line":12,"column":37},"end":{"line":12,"column":95}},"line":12},"1":{"name":"toElement","decl":{"start":{"line":14,"column":45},"end":{"line":14,"column":54}},"loc":{"start":{"line":14,"column":60},"end":{"line":25,"column":1}},"line":14},"2":{"name":"(anonymous_2)","decl":{"start":{"line":18,"column":64},"end":{"line":18,"column":65}},"loc":{"start":{"line":18,"column":81},"end":{"line":20,"column":3}},"line":18},"3":{"name":"getName","decl":{"start":{"line":27,"column":41},"end":{"line":27,"column":48}},"loc":{"start":{"line":27,"column":55},"end":{"line":29,"column":1}},"line":27},"4":{"name":"omit","decl":{"start":{"line":31,"column":35},"end":{"line":31,"column":39}},"loc":{"start":{"line":31,"column":51},"end":{"line":38,"column":1}},"line":31},"5":{"name":"toObject","decl":{"start":{"line":40,"column":43},"end":{"line":40,"column":51}},"loc":{"start":{"line":40,"column":56},"end":{"line":60,"column":1}},"line":40},"6":{"name":"(anonymous_6)","decl":{"start":{"line":51,"column":64},"end":{"line":51,"column":65}},"loc":{"start":{"line":51,"column":81},"end":{"line":53,"column":3}},"line":51}},"branchMap":{"0":{"loc":{"start":{"line":12,"column":46},"end":{"line":12,"column":92}},"type":"cond-expr","locations":[{"start":{"line":12,"column":70},"end":{"line":12,"column":73}},{"start":{"line":12,"column":76},"end":{"line":12,"column":92}}],"line":12},"1":{"loc":{"start":{"line":12,"column":46},"end":{"line":12,"column":67}},"type":"binary-expr","locations":[{"start":{"line":12,"column":46},"end":{"line":12,"column":49}},{"start":{"line":12,"column":53},"end":{"line":12,"column":67}}],"line":12},"2":{"loc":{"start":{"line":15,"column":14},"end":{"line":15,"column":84}},"type":"cond-expr","locations":[{"start":{"line":15,"column":67},"end":{"line":15,"column":79}},{"start":{"line":15,"column":82},"end":{"line":15,"column":84}}],"line":15},"3":{"loc":{"start":{"line":15,"column":14},"end":{"line":15,"column":64}},"type":"binary-expr","locations":[{"start":{"line":15,"column":14},"end":{"line":15,"column":34}},{"start":{"line":15,"column":38},"end":{"line":15,"column":64}}],"line":15},"4":{"loc":{"start":{"line":17,"column":2},"end":{"line":17,"column":42}},"type":"if","locations":[{"start":{"line":17,"column":2},"end":{"line":17,"column":42}},{"start":{"line":17,"column":2},"end":{"line":17,"column":42}}],"line":17},"5":{"loc":{"start":{"line":18,"column":17},"end":{"line":20,"column":19}},"type":"cond-expr","locations":[{"start":{"line":18,"column":47},"end":{"line":20,"column":4}},{"start":{"line":20,"column":7},"end":{"line":20,"column":19}}],"line":18},"6":{"loc":{"start":{"line":21,"column":13},"end":{"line":21,"column":40}},"type":"binary-expr","locations":[{"start":{"line":21,"column":13},"end":{"line":21,"column":28}},{"start":{"line":21,"column":32},"end":{"line":21,"column":40}}],"line":21},"7":{"loc":{"start":{"line":22,"column":14},"end":{"line":22,"column":31}},"type":"binary-expr","locations":[{"start":{"line":22,"column":14},"end":{"line":22,"column":23}},{"start":{"line":22,"column":27},"end":{"line":22,"column":31}}],"line":22},"8":{"loc":{"start":{"line":28,"column":9},"end":{"line":28,"column":89}},"type":"cond-expr","locations":[{"start":{"line":28,"column":38},"end":{"line":28,"column":82}},{"start":{"line":28,"column":85},"end":{"line":28,"column":89}}],"line":28},"9":{"loc":{"start":{"line":28,"column":38},"end":{"line":28,"column":82}},"type":"binary-expr","locations":[{"start":{"line":28,"column":38},"end":{"line":28,"column":54}},{"start":{"line":28,"column":58},"end":{"line":28,"column":67}},{"start":{"line":28,"column":71},"end":{"line":28,"column":82}}],"line":28},"10":{"loc":{"start":{"line":34,"column":4},"end":{"line":34,"column":37}},"type":"if","locations":[{"start":{"line":34,"column":4},"end":{"line":34,"column":37}},{"start":{"line":34,"column":4},"end":{"line":34,"column":37}}],"line":34},"11":{"loc":{"start":{"line":41,"column":2},"end":{"line":41,"column":40}},"type":"if","locations":[{"start":{"line":41,"column":2},"end":{"line":41,"column":40}},{"start":{"line":41,"column":2},"end":{"line":41,"column":40}}],"line":41},"12":{"loc":{"start":{"line":45,"column":2},"end":{"line":50,"column":3}},"type":"if","locations":[{"start":{"line":45,"column":2},"end":{"line":50,"column":3}},{"start":{"line":45,"column":2},"end":{"line":50,"column":3}}],"line":45},"13":{"loc":{"start":{"line":45,"column":6},"end":{"line":45,"column":31}},"type":"binary-expr","locations":[{"start":{"line":45,"column":6},"end":{"line":45,"column":12}},{"start":{"line":45,"column":16},"end":{"line":45,"column":31}}],"line":45}},"s":{"0":1,"1":1,"2":1,"3":1,"4":1,"5":1,"6":6,"7":6,"8":2,"9":4,"10":4,"11":4,"12":4,"13":4,"14":4,"15":1,"16":5,"17":1,"18":4,"19":4,"20":6,"21":4,"22":2,"23":4,"24":1,"25":7,"26":2,"27":5,"28":5,"29":5,"30":1,"31":4,"32":4,"33":4},"f":{"0":1,"1":6,"2":4,"3":5,"4":4,"5":7,"6":4},"b":{"0":[0,1],"1":[1,1],"2":[5,1],"3":[6,5],"4":[2,4],"5":[4,0],"6":[4,3],"7":[4,2],"8":[1,4],"9":[1,0,0],"10":[4,2],"11":[2,5],"12":[1,4],"13":[5,5]},"_coverageSchema":"332fd63041d2c1bcb487cc26dd0d5f7d97098a6c","hash":"b16e3054524fd3e7103801e401acb0717c96bd90","contentHash":"071c7bcce68792eaf1e74a3ea46f1517_11.4.1"}} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # json-react 3 | 4 | Convert objects to React elements and elements to serializable objects 5 | 6 | ```sh 7 | npm i json-react 8 | ``` 9 | 10 | Convert React element to object 11 | 12 | ```js 13 | import jsonReact from 'json-react' 14 | 15 | const el = ( 16 |
17 |

Hello

18 |
19 | ) 20 | 21 | const obj = jsonReact.toObject(el) 22 | ``` 23 | 24 | ```js 25 | // returns 26 | { 27 | type: 'div', 28 | props: null, 29 | children: [ 30 | { 31 | type: 'h1', 32 | props: null, 33 | children: [ 34 | 'Hello' 35 | ] 36 | } 37 | ] 38 | } 39 | ``` 40 | 41 | Convert object to React element 42 | 43 | ```js 44 | import jsonReact from 'json-react' 45 | 46 | const el = jsonReact.toElement({ 47 | type: 'div', 48 | props: null, 49 | children: [ 50 | { 51 | type: 'h1', 52 | props: null, 53 | children: [ 54 | 'Hello' 55 | ] 56 | } 57 | ] 58 | }) 59 | ``` 60 | 61 | ```js 62 | // returns 63 |
64 |

Hello

65 |
66 | ``` 67 | 68 | Convert object to React element with references to components 69 | 70 | ```js 71 | import jsonReact from 'json-react' 72 | import MyComponent from './MyComponent' 73 | 74 | const el = jsonReact.toElement({ 75 | type: 'MyComponent', 76 | props: {}, 77 | children: [ 78 | 'Hello' 79 | ] 80 | }, { MyComponent }) 81 | 82 | // Hello 83 | ``` 84 | 85 | ## Why? 86 | 87 | - Demonstrate how React elements are objects and can be converted to and from JSON 88 | - Components can be serialized by displayName 89 | - Components can be passed as scope to create elements from objects 90 | 91 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import React, { 2 | createElement, 3 | Children 4 | } from 'react' 5 | 6 | export const toElement = (obj, scope = {}) => { 7 | if (typeof obj === 'string') return obj 8 | const children = Array.isArray(obj.children) 9 | ? obj.children.map(child => toElement(child, scope)) 10 | : obj.children 11 | const type = scope[obj.type] || obj.type 12 | const props = obj.props || null 13 | const el = createElement(type, props, children) 14 | return el 15 | } 16 | 17 | export const getName = type => typeof type === 'function' 18 | ? type.displayName || type.name || 'Component' 19 | : type 20 | 21 | export const omit = (obj, keys) => { 22 | const next = {} 23 | for (let key in obj) { 24 | if (keys.includes(key)) continue 25 | next[key] = obj[key] 26 | } 27 | return next 28 | } 29 | 30 | export const toObject = el => { 31 | if (typeof el === 'string') return el 32 | const type = getName(el.type) 33 | const props = el.props 34 | 35 | if (!props || !props.children) { 36 | return { 37 | type, 38 | props 39 | } 40 | } 41 | const children = Children.toArray(el.props.children) 42 | .map(child => toObject(child)) 43 | 44 | return { 45 | type, 46 | props: omit(props, [ 'children' ]), 47 | children 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json-react", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "dist.js", 6 | "module": "index", 7 | "scripts": { 8 | "prepublish": "babel index.js -o dist.js", 9 | "test": "nyc ava" 10 | }, 11 | "keywords": [], 12 | "author": "Brent Jackson", 13 | "license": "MIT", 14 | "dependencies": { 15 | "react": "^16.2.0" 16 | }, 17 | "devDependencies": { 18 | "ava": "^0.25.0", 19 | "babel-cli": "^6.26.0", 20 | "babel-preset-env": "^1.6.1", 21 | "babel-preset-react": "^6.24.1", 22 | "babel-preset-stage-0": "^6.24.1", 23 | "babel-register": "^6.26.0", 24 | "nyc": "^11.4.1", 25 | "react-test-renderer": "^16.2.0" 26 | }, 27 | "ava": { 28 | "require": [ 29 | "babel-register" 30 | ], 31 | "babel": "inherit" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import React from 'react' 3 | import { create as render } from 'react-test-renderer' 4 | import { toElement, toObject } from '.' 5 | 6 | test('converts object to React element', t => { 7 | const el = toElement({ 8 | type: 'div', 9 | props: null, 10 | children: [ 11 | { 12 | type: 'h1', 13 | props: { 14 | id: 'hi' 15 | }, 16 | children: [ 17 | 'Hello' 18 | ] 19 | } 20 | ] 21 | }) 22 | const json = render(el).toJSON() 23 | t.is(json.type, 'div') 24 | t.deepEqual(json.props, {}) 25 | t.is(json.children.length, 1) 26 | t.is(json.children[0].type, 'h1') 27 | t.deepEqual(json.children[0].props, { 28 | id: 'hi' 29 | }) 30 | t.is(json.children[0].children[0], 'Hello') 31 | }) 32 | 33 | test('converts object to React element with scope', t => { 34 | const scope = { 35 | Hello: props =>

36 | } 37 | const el = toElement({ 38 | type: 'div', 39 | props: null, 40 | children: [ 41 | { 42 | type: 'Hello', 43 | props: { 44 | id: 'beep' 45 | }, 46 | children: [ 'Beep' ] 47 | } 48 | ] 49 | }, scope) 50 | const json = render(el).toJSON() 51 | t.is(json.type, 'div') 52 | t.is(el.props.children[0].type, scope.Hello) 53 | }) 54 | 55 | test('converts React element to object', t => { 56 | const obj = toObject( 57 |
58 |

Hi

59 |
60 | ) 61 | t.is(obj.type, 'header') 62 | t.is(obj.props.className, 'beep') 63 | t.is(obj.children[0].type, 'h2') 64 | t.is(obj.children[0].props.id, '2') 65 | t.is(obj.children[0].children[0], 'Hi') 66 | }) 67 | 68 | test('converts empty element to object', t => { 69 | const obj = toObject(
) 70 | t.is(obj.type, 'div') 71 | }) 72 | 73 | test('converts element with components to object', t => { 74 | const Hello = props =>

75 | Hello.displayName = 'Hello' 76 | const obj = toObject( 77 |
78 | Hi 79 |
80 | ) 81 | const [ child ] = obj.children 82 | t.is(child.type, 'Hello') 83 | t.is(child.children[0], 'Hi') 84 | }) 85 | 86 | --------------------------------------------------------------------------------