├── .eslintignore ├── .gitignore ├── .editorconfig ├── README.md ├── JavaScript ├── 1-simple.js ├── 9-optimized.js ├── 3-cached.js ├── 2-extended.js ├── 4-imperative.js ├── 5-lense.js ├── a-display.js ├── b-disp-opt.js ├── c-filter.js ├── 7-declarative.js ├── 6-details.js └── 8-syntax.js ├── LICENSE └── .eslintrc.json /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | charset = utf-8 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [{*.js,*.mjs,*.ts,*.json,*.yml}] 11 | indent_size = 2 12 | indent_style = space 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Data structures projection 2 | 3 | [Задать вопрос](https://github.com/HowProgrammingWorks/LiveQA/discussions/categories/q-a) 4 | 5 | [![Проекции и отображения наборов данных](https://img.youtube.com/vi/lwJCq9inky8/0.jpg)](https://www.youtube.com/watch?v=lwJCq9inky8) 6 | -------------------------------------------------------------------------------- /JavaScript/1-simple.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const partial = (fn, ...args) => (...rest) => fn(...args, ...rest); 4 | 5 | const projection = (fields, obj) => Object.keys(obj) 6 | .filter((field) => fields.includes(field)) 7 | .reduce((hash, key) => (hash[key] = obj[key], hash), {}); 8 | 9 | // Dataset 10 | 11 | const persons = [ 12 | { name: 'Marcus Aurelius', city: 'Rome', born: 121 }, 13 | { name: 'Victor Glushkov', city: 'Rostov on Don', born: 1923 }, 14 | { name: 'Ibn Arabi', city: 'Murcia', born: 1165 }, 15 | { name: 'Mao Zedong', city: 'Shaoshan', born: 1893 }, 16 | { name: 'Rene Descartes', city: 'La Haye en Touraine', born: 1596 }, 17 | ]; 18 | 19 | // Usage 20 | 21 | const p1 = partial(projection, ['name', 'born']); 22 | const p2 = partial(projection, ['name']); 23 | 24 | const data = persons.map(p1).map(p2); 25 | console.dir(data); 26 | -------------------------------------------------------------------------------- /JavaScript/9-optimized.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Projection 4 | 5 | const id = (x) => x; 6 | 7 | const projection = (meta) => (src) => meta.reduce( 8 | (dest, [name, fn = id, field = name]) => 9 | (dest[name] = fn(src[field]), dest), {} 10 | ); 11 | 12 | // Dataset 13 | 14 | const persons = [ 15 | { name: 'Marcus Aurelius', city: 'Rome', born: 121 }, 16 | { name: 'Victor Glushkov', city: 'Rostov on Don', born: 1923 }, 17 | { name: 'Ibn Arabi', city: 'Murcia', born: 1165 }, 18 | { name: 'Mao Zedong', city: 'Shaoshan', born: 1893 }, 19 | { name: 'Rene Descartes', city: 'La Haye en Touraine', born: 1596 }, 20 | ]; 21 | 22 | // Metadata 23 | 24 | const year = (date) => date.getFullYear(); 25 | const diff = (y) => year(new Date()) - year(new Date(y.toString())); 26 | const upper = (s) => s.toUpperCase(); 27 | 28 | const md = [ 29 | ['name'], 30 | ['place', upper, 'city'], 31 | ['age', diff, 'born'], 32 | ]; 33 | 34 | // Usage 35 | 36 | const p1 = projection(md); 37 | const data = persons.map(p1); 38 | console.dir(data); 39 | -------------------------------------------------------------------------------- /JavaScript/3-cached.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Projection 4 | 5 | const projection = (meta) => { 6 | const keys = Object.keys(meta); 7 | return (obj) => keys.reduce((hash, key) => ( 8 | hash[key] = meta[key] 9 | .reduce((val, fn, i) => (i ? fn(val) : obj[fn]), null), 10 | hash), {}); 11 | }; 12 | 13 | // Dataset 14 | 15 | const persons = [ 16 | { name: 'Marcus Aurelius', city: 'Rome', born: 121 }, 17 | { name: 'Victor Glushkov', city: 'Rostov on Don', born: 1923 }, 18 | { name: 'Ibn Arabi', city: 'Murcia', born: 1165 }, 19 | { name: 'Mao Zedong', city: 'Shaoshan', born: 1893 }, 20 | { name: 'Rene Descartes', city: 'La Haye en Touraine', born: 1596 }, 21 | ]; 22 | 23 | // Metadata 24 | 25 | const md = { 26 | name: ['name'], 27 | place: ['city', (s) => '<' + s.toUpperCase() + '>'], 28 | age: ['born', (year) => ( 29 | new Date().getFullYear() - new Date(year.toString()).getFullYear() 30 | )] 31 | }; 32 | 33 | // Usage 34 | 35 | const p1 = projection(md); 36 | const data = persons.map(p1); 37 | console.dir(data); 38 | -------------------------------------------------------------------------------- /JavaScript/2-extended.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const partial = (fn, ...args) => (...rest) => fn(...args, ...rest); 4 | 5 | // Projection 6 | 7 | const projection = (meta, obj) => Object.keys(meta) 8 | .reduce((hash, key) => (hash[key] = meta[key] 9 | .reduce((val, fn, i) => (i ? fn(val) : obj[fn]), null), hash), {}); 10 | 11 | // Dataset 12 | 13 | const persons = [ 14 | { name: 'Marcus Aurelius', city: 'Rome', born: 121 }, 15 | { name: 'Victor Glushkov', city: 'Rostov on Don', born: 1923 }, 16 | { name: 'Ibn Arabi', city: 'Murcia', born: 1165 }, 17 | { name: 'Mao Zedong', city: 'Shaoshan', born: 1893 }, 18 | { name: 'Rene Descartes', city: 'La Haye en Touraine', born: 1596 }, 19 | ]; 20 | 21 | // Metadata 22 | 23 | const md = { 24 | name: ['name'], 25 | place: ['city', (s) => '<' + s.toUpperCase() + '>'], 26 | age: ['born', (year) => ( 27 | new Date().getFullYear() - new Date(year.toString()).getFullYear() 28 | )] 29 | }; 30 | 31 | // Usage 32 | 33 | const p1 = partial(projection, md); 34 | const data = persons.map(p1); 35 | console.dir(data); 36 | -------------------------------------------------------------------------------- /JavaScript/4-imperative.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Projection 4 | 5 | const projection = (meta) => { 6 | const keys = Object.keys(meta); 7 | return (obj) => { 8 | const hash = {}; 9 | keys.forEach((key) => { 10 | const def = meta[key]; 11 | const [field, fn] = def; 12 | const val = obj[field]; 13 | hash[key] = fn ? fn(val) : val; 14 | }); 15 | return hash; 16 | }; 17 | }; 18 | 19 | // Dataset 20 | 21 | const persons = [ 22 | { name: 'Marcus Aurelius', city: 'Rome', born: 121 }, 23 | { name: 'Victor Glushkov', city: 'Rostov on Don', born: 1923 }, 24 | { name: 'Ibn Arabi', city: 'Murcia', born: 1165 }, 25 | { name: 'Mao Zedong', city: 'Shaoshan', born: 1893 }, 26 | { name: 'Rene Descartes', city: 'La Haye en Touraine', born: 1596 }, 27 | ]; 28 | 29 | // Metadata 30 | 31 | const md = { 32 | name: ['name'], 33 | place: ['city', (s) => `<${s.toUpperCase()}>`], 34 | age: ['born', (year) => ( 35 | new Date().getFullYear() - 36 | new Date(year.toString()).getFullYear() 37 | )] 38 | }; 39 | 40 | // Usage 41 | 42 | const p1 = projection(md); 43 | const data = persons.map(p1); 44 | console.dir(data); 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2022 How.Programming.Works contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /JavaScript/5-lense.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Lens 4 | 5 | const view = (lens, obj) => lens.get(obj); 6 | 7 | const lens = (source, destination = source) => ({ 8 | get: (obj) => obj[source], 9 | set: (val, obj) => ({ ...obj, [destination]: val }) 10 | }); 11 | 12 | const id = (x) => x; 13 | 14 | const field = (name, map = id) => (obj) => map(view(lens(name), obj)); 15 | 16 | // Projection 17 | 18 | const projection = (meta) => { 19 | const keys = Object.keys(meta); 20 | return (obj) => { 21 | const hash = {}; 22 | keys.forEach((key) => { 23 | const field = meta[key]; 24 | hash[key] = field(obj); 25 | }); 26 | return hash; 27 | }; 28 | }; 29 | 30 | // Dataset 31 | 32 | const persons = [ 33 | { name: 'Marcus Aurelius', city: 'Rome', born: 121 }, 34 | { name: 'Victor Glushkov', city: 'Rostov on Don', born: 1923 }, 35 | { name: 'Ibn Arabi', city: 'Murcia', born: 1165 }, 36 | { name: 'Mao Zedong', city: 'Shaoshan', born: 1893 }, 37 | { name: 'Rene Descartes', city: 'La Haye en Touraine', born: 1596 }, 38 | ]; 39 | 40 | // Metadata 41 | 42 | const age = (year) => 43 | new Date().getFullYear() - 44 | new Date(year.toString()).getFullYear(); 45 | 46 | const upper = (s) => s.toUpperCase(); 47 | 48 | const md = { 49 | name: field('name'), 50 | place: field('city', upper), 51 | age: field('born', age) 52 | }; 53 | 54 | // Usage 55 | 56 | const p1 = projection(md); 57 | const data = persons.map(p1); 58 | console.dir(data); 59 | -------------------------------------------------------------------------------- /JavaScript/a-display.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Projection 4 | 5 | const id = (x) => x; 6 | 7 | const projection = (meta) => (src) => meta.reduce( 8 | (dest, [name, fn = id, field = name]) => 9 | (dest[name] = fn(src[field]), dest), {} 10 | ); 11 | 12 | // Display 13 | 14 | const max = (a, b) => (a > b ? a : b); 15 | 16 | const render = (meta) => (src) => { 17 | const keys = meta.map(([name]) => name); 18 | const width = src.map((obj) => keys.map( 19 | (key) => obj[key].toString().length 20 | )); 21 | const maxWidth = keys.map( 22 | (key, i) => width.reduce((a, b) => max(a, b[i]), 0) 23 | ); 24 | const dest = src.map((obj) => keys.map( 25 | (key, i) => obj[key].toString().padEnd(maxWidth[i] + 4) 26 | )); 27 | return dest.map((row) => row.join('')).join('\n'); 28 | }; 29 | 30 | // Dataset 31 | 32 | const persons = [ 33 | { name: 'Marcus Aurelius', city: 'Rome', born: 121 }, 34 | { name: 'Victor Glushkov', city: 'Rostov on Don', born: 1923 }, 35 | { name: 'Ibn Arabi', city: 'Murcia', born: 1165 }, 36 | { name: 'Mao Zedong', city: 'Shaoshan', born: 1893 }, 37 | { name: 'Rene Descartes', city: 'La Haye en Touraine', born: 1596 }, 38 | ]; 39 | 40 | // Metadata 41 | 42 | const year = (date) => date.getFullYear(); 43 | const diff = (y) => year(new Date()) - year(new Date(y.toString())); 44 | const upper = (s) => s.toUpperCase(); 45 | 46 | const md = [ 47 | ['name'], 48 | ['place', upper, 'city'], 49 | ['age', diff, 'born'], 50 | ]; 51 | 52 | // Usage 53 | 54 | const pf = projection(md); 55 | const data = persons.map(pf); 56 | 57 | const renderer = render(md); 58 | const res = renderer(data); 59 | console.log('\n' + res + '\n'); 60 | -------------------------------------------------------------------------------- /JavaScript/b-disp-opt.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Projection 4 | 5 | const id = (x) => x; 6 | 7 | const projection = (meta) => (src) => meta.reduce( 8 | (dest, [name, fn = id, field = name]) => 9 | (dest[name] = fn(src[field]), dest), {} 10 | ); 11 | 12 | // Display 13 | 14 | const max = (items) => Math.max(...items); 15 | const maxProp = (key) => (items) => max(items.map((x) => x[key])); 16 | const maxLength = maxProp('length'); 17 | const col = (name, data) => data.map((obj) => obj[name].toString()); 18 | 19 | const render = (meta) => (src) => { 20 | const keys = meta.map(([name]) => name); 21 | const maxWidth = keys.map((key) => maxLength(col(key, src))); 22 | const dest = src.map((obj) => maxWidth.map( 23 | (width, i) => obj[keys[i]].toString().padEnd(width + 4) 24 | )); 25 | return dest.map((row) => row.join('')).join('\n'); 26 | }; 27 | 28 | // Dataset 29 | 30 | const persons = [ 31 | { name: 'Marcus Aurelius', city: 'Rome', born: 121 }, 32 | { name: 'Victor Glushkov', city: 'Rostov on Don', born: 1923 }, 33 | { name: 'Ibn Arabi', city: 'Murcia', born: 1165 }, 34 | { name: 'Mao Zedong', city: 'Shaoshan', born: 1893 }, 35 | { name: 'Rene Descartes', city: 'La Haye en Touraine', born: 1596 }, 36 | ]; 37 | 38 | // Metadata 39 | 40 | const year = (date) => date.getFullYear(); 41 | const diff = (y) => year(new Date()) - year(new Date(y.toString())); 42 | const upper = (s) => s.toUpperCase(); 43 | 44 | const md = [ 45 | ['name'], 46 | ['place', upper, 'city'], 47 | ['age', diff, 'born'], 48 | ]; 49 | 50 | // Usage 51 | 52 | const pf = projection(md); 53 | const data = persons.map(pf); 54 | 55 | const renderer = render(md); 56 | const res = renderer(data); 57 | console.log('\n' + res + '\n'); 58 | -------------------------------------------------------------------------------- /JavaScript/c-filter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Projection 4 | 5 | const id = (x) => x; 6 | 7 | const projection = (meta) => (src) => meta.reduce( 8 | (dest, [name, fn = id, field = name]) => 9 | (dest[name] = fn(src[field]), dest), {} 10 | ); 11 | 12 | // Display 13 | 14 | const max = (items) => Math.max(...items); 15 | const maxProp = (key) => (items) => max(items.map((x) => x[key])); 16 | const maxLength = maxProp('length'); 17 | const col = (name, data) => data.map((obj) => obj[name].toString()); 18 | 19 | const render = (meta) => (src) => { 20 | const keys = meta.map(([name]) => name); 21 | const maxWidth = keys.map((key) => maxLength(col(key, src))); 22 | const dest = src.map((obj) => maxWidth.map( 23 | (width, i) => obj[keys[i]].toString().padEnd(width + 4) 24 | )); 25 | return dest.map((row) => row.join('')).join('\n'); 26 | }; 27 | 28 | // Dataset 29 | 30 | const persons = [ 31 | { name: 'Marcus Aurelius', city: 'Rome', born: 121 }, 32 | { name: 'Victor Glushkov', city: 'Rostov on Don', born: 1923 }, 33 | { name: 'Ibn Arabi', city: 'Murcia', born: 1165 }, 34 | { name: 'Mao Zedong', city: 'Shaoshan', born: 1893 }, 35 | { name: 'Rene Descartes', city: 'La Haye en Touraine', born: 1596 }, 36 | ]; 37 | 38 | // Metadata 39 | 40 | const year = (date) => date.getFullYear(); 41 | const diff = (y) => year(new Date()) - year(new Date(y.toString())); 42 | const upper = (s) => s.toUpperCase(); 43 | 44 | const md = [ 45 | ['name'], 46 | ['place', upper, 'city'], 47 | ['age', diff, 'born'], 48 | ]; 49 | 50 | const query = (person) => ( 51 | person.name !== '' && 52 | person.born < 1900 && 53 | person.city === 'Rome' 54 | ); 55 | 56 | // Usage 57 | 58 | const pf = projection(md); 59 | const data = persons.filter(query).map(pf); 60 | 61 | const renderer = render(md); 62 | const res = renderer(data); 63 | console.log('\n' + res + '\n'); 64 | -------------------------------------------------------------------------------- /JavaScript/7-declarative.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Projection 4 | 5 | const projection = (meta) => { 6 | const keys = Object.keys(meta); 7 | return (obj) => { 8 | const hash = {}; 9 | for (const key of keys) { 10 | const def = meta[key]; 11 | const [name, transform] = def; 12 | let val = obj[name]; 13 | if (val) { 14 | if (transform) { 15 | val = typeof transform === 'function' ? 16 | transform(val) : val.map(projection(transform)); 17 | } 18 | hash[key] = val; 19 | } 20 | } 21 | return hash; 22 | }; 23 | }; 24 | 25 | // Dataset 26 | 27 | const persons = [ 28 | { name: 'Marcus Aurelius', city: 'Rome', born: 121, places: [ 29 | { name: 'Shanghai', population: 24256800, country: 'China' }, 30 | { name: 'Beijing', population: 21516000, country: 'China' }, 31 | { name: 'Delhi', population: 16787941, country: 'India' } 32 | ] }, 33 | { name: 'Victor Glushkov', city: 'Rostov on Don', born: 1923, places: [ 34 | { name: 'Lagos', population: 16060303, country: 'Nigeria' }, 35 | { name: 'Delhi', population: 16787941, country: 'India' }, 36 | { name: 'Tianjin', population: 15200000, country: 'China' } 37 | ] }, 38 | { name: 'Ibn Arabi', city: 'Murcia', born: 1165, places: [ 39 | { name: 'Beijing' } 40 | ] }, 41 | { name: 'Mao Zedong', city: 'Shaoshan', born: 1893 }, 42 | { name: 'Rene Descartes', city: 'La Haye en Touraine', born: 1596, places: [ 43 | { name: 'Karachi', population: 14910352, country: 'Pakistan' }, 44 | { name: 'Istanbul', population: 14160467, country: 'Turkey' }, 45 | { name: 'Tianjin', population: 15200000, country: 'China' } 46 | ] }, 47 | ]; 48 | 49 | // Metadata 50 | 51 | const md = { 52 | name: ['name'], 53 | place: ['city', (s) => `<${s.toUpperCase()}>`], 54 | born: ['born'], 55 | age: ['born', (year) => ( 56 | new Date().getFullYear() - 57 | new Date(year.toString()).getFullYear() 58 | )], 59 | places: ['places', { 60 | address: ['name', (s) => s.toUpperCase()], 61 | population: ['population'] 62 | }] 63 | }; 64 | 65 | // Usage 66 | 67 | const p = projection(md); 68 | const data = persons.map(p); 69 | console.dir(data, { depth: 10 }); 70 | -------------------------------------------------------------------------------- /JavaScript/6-details.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Projection 4 | 5 | const projection = (meta) => { 6 | const keys = Object.keys(meta); 7 | const mapper = (obj) => { 8 | const hash = {}; 9 | for (const key of keys) { 10 | const def = meta[key]; 11 | const [name, fn] = def; 12 | let val = obj[name]; 13 | if (val) { 14 | if (fn) val = fn(val); 15 | hash[key] = val; 16 | } 17 | } 18 | return hash; 19 | }; 20 | mapper.join = (key, projection) => { 21 | keys.push(key); 22 | meta[key] = [key, (val) => val.map(projection)]; 23 | return mapper; 24 | }; 25 | return mapper; 26 | }; 27 | 28 | // Dataset 29 | 30 | const persons = [ 31 | { name: 'Marcus Aurelius', city: 'Rome', born: 121, places: [ 32 | { name: 'Shanghai', population: 24256800, country: 'China' }, 33 | { name: 'Beijing', population: 21516000, country: 'China' }, 34 | { name: 'Delhi', population: 16787941, country: 'India' } 35 | ] }, 36 | { name: 'Victor Glushkov', city: 'Rostov on Don', born: 1923, places: [ 37 | { name: 'Lagos', population: 16060303, country: 'Nigeria' }, 38 | { name: 'Delhi', population: 16787941, country: 'India' }, 39 | { name: 'Tianjin', population: 15200000, country: 'China' } 40 | ] }, 41 | { name: 'Ibn Arabi', city: 'Murcia', born: 1165, places: [ 42 | { name: 'Beijing' } 43 | ] }, 44 | { name: 'Mao Zedong', city: 'Shaoshan', born: 1893 }, 45 | { name: 'Rene Descartes', city: 'La Haye en Touraine', born: 1596, places: [ 46 | { name: 'Karachi', population: 14910352, country: 'Pakistan' }, 47 | { name: 'Istanbul', population: 14160467, country: 'Turkey' }, 48 | { name: 'Tianjin', population: 15200000, country: 'China' } 49 | ] }, 50 | ]; 51 | 52 | // Metadata 53 | 54 | const md1 = { 55 | name: ['name'], 56 | place: ['city', (s) => `<${s.toUpperCase()}>`], 57 | born: ['born'], 58 | age: ['born', (year) => ( 59 | new Date().getFullYear() - 60 | new Date(year.toString()).getFullYear() 61 | )] 62 | }; 63 | 64 | const md2 = { 65 | address: ['name', (s) => s.toUpperCase()], 66 | population: ['population'] 67 | }; 68 | 69 | // Usage 70 | 71 | const p1 = projection(md1); 72 | const p2 = projection(md2); 73 | const p3 = p1.join('places', p2); 74 | const data = persons.map(p3); 75 | console.dir(data, { depth: 10 }); 76 | -------------------------------------------------------------------------------- /JavaScript/8-syntax.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Projection 4 | 5 | const projection = (metadata) => { 6 | const meta = {}; 7 | let key; 8 | for (const item of metadata) { 9 | const type = typeof item; 10 | let cast = projection[type]; 11 | if (type === 'string') key = item; 12 | if (type === 'object') cast = cast(key); 13 | meta[key] = cast(item); 14 | } 15 | const keys = Object.keys(meta); 16 | const mapper = (obj) => { 17 | const hash = {}; 18 | for (const key of keys) { 19 | const cast = meta[key]; 20 | const value = cast(obj); 21 | if (value) hash[key] = value; 22 | } 23 | return hash; 24 | }; 25 | return mapper; 26 | }; 27 | 28 | projection.string = (name) => (d) => d[name]; 29 | projection.function = (fn) => (d) => fn(d); 30 | projection.object = (name) => (proj) => (d) => { 31 | const data = d[name]; 32 | if (!data) return data; 33 | return data.map(projection(proj)); 34 | }; 35 | 36 | // Dataset 37 | 38 | const persons = [ 39 | { name: 'Marcus Aurelius', city: 'Rome', born: 121, places: [ 40 | { name: 'Shanghai', population: 24256800, country: 'China' }, 41 | { name: 'Beijing', population: 21516000, country: 'China' }, 42 | { name: 'Delhi', population: 16787941, country: 'India' } 43 | ] }, 44 | { name: 'Victor Glushkov', city: 'Rostov on Don', born: 1923, places: [ 45 | { name: 'Lagos', population: 16060303, country: 'Nigeria' }, 46 | { name: 'Delhi', population: 16787941, country: 'India' }, 47 | { name: 'Tianjin', population: 15200000, country: 'China' } 48 | ] }, 49 | { name: 'Ibn Arabi', city: 'Murcia', born: 1165, places: [ 50 | { name: 'Beijing', population: 21516000, country: 'China' }, 51 | ] }, 52 | { name: 'Mao Zedong', city: 'Shaoshan', born: 1893 }, 53 | { name: 'Rene Descartes', city: 'La Haye en Touraine', born: 1596, places: [ 54 | { name: 'Karachi', population: 14910352, country: 'Pakistan' }, 55 | { name: 'Istanbul', population: 14160467, country: 'Turkey' }, 56 | { name: 'Tianjin', population: 15200000, country: 'China' } 57 | ] }, 58 | ]; 59 | 60 | // Metadata 61 | 62 | const md = [ 63 | 'name', 64 | 'place', (d) => `<${d.city.toUpperCase()}>`, 65 | 'born', 66 | 'age', (d) => ( 67 | new Date().getFullYear() - 68 | new Date(d.born.toString()).getFullYear() 69 | ), 70 | 'places', [ 71 | 'address', (d) => (d.country.toUpperCase() + ', ' + d.name), 72 | 'population' 73 | ] 74 | ]; 75 | 76 | // Usage 77 | 78 | const p = projection(md); 79 | const data = persons.map(p); 80 | console.dir(data, { depth: 10 }); 81 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "extends": "eslint:recommended", 8 | "parserOptions": { 9 | "ecmaVersion": "latest" 10 | }, 11 | "globals": { 12 | "BigInt": true 13 | }, 14 | "rules": { 15 | "indent": [ 16 | "error", 17 | 2 18 | ], 19 | "linebreak-style": [ 20 | "error", 21 | "unix" 22 | ], 23 | "quotes": [ 24 | "error", 25 | "single" 26 | ], 27 | "semi": [ 28 | "error", 29 | "always" 30 | ], 31 | "no-loop-func": [ 32 | "error" 33 | ], 34 | "block-spacing": [ 35 | "error", 36 | "always" 37 | ], 38 | "camelcase": [ 39 | "error" 40 | ], 41 | "eqeqeq": [ 42 | "error", 43 | "always" 44 | ], 45 | "strict": [ 46 | "error", 47 | "global" 48 | ], 49 | "brace-style": [ 50 | "error", 51 | "1tbs", 52 | { 53 | "allowSingleLine": true 54 | } 55 | ], 56 | "comma-style": [ 57 | "error", 58 | "last" 59 | ], 60 | "comma-spacing": [ 61 | "error", 62 | { 63 | "before": false, 64 | "after": true 65 | } 66 | ], 67 | "eol-last": [ 68 | "error" 69 | ], 70 | "func-call-spacing": [ 71 | "error", 72 | "never" 73 | ], 74 | "key-spacing": [ 75 | "error", 76 | { 77 | "beforeColon": false, 78 | "afterColon": true, 79 | "mode": "minimum" 80 | } 81 | ], 82 | "keyword-spacing": [ 83 | "error", 84 | { 85 | "before": true, 86 | "after": true, 87 | "overrides": { 88 | "function": { 89 | "after": false 90 | } 91 | } 92 | } 93 | ], 94 | "max-len": [ 95 | "error", 96 | { 97 | "code": 80, 98 | "ignoreUrls": true 99 | } 100 | ], 101 | "max-nested-callbacks": [ 102 | "error", 103 | { 104 | "max": 7 105 | } 106 | ], 107 | "new-cap": [ 108 | "error", 109 | { 110 | "newIsCap": true, 111 | "capIsNew": false, 112 | "properties": true 113 | } 114 | ], 115 | "new-parens": [ 116 | "error" 117 | ], 118 | "no-lonely-if": [ 119 | "error" 120 | ], 121 | "no-trailing-spaces": [ 122 | "error" 123 | ], 124 | "no-unneeded-ternary": [ 125 | "error" 126 | ], 127 | "no-whitespace-before-property": [ 128 | "error" 129 | ], 130 | "object-curly-spacing": [ 131 | "error", 132 | "always" 133 | ], 134 | "operator-assignment": [ 135 | "error", 136 | "always" 137 | ], 138 | "operator-linebreak": [ 139 | "error", 140 | "after" 141 | ], 142 | "semi-spacing": [ 143 | "error", 144 | { 145 | "before": false, 146 | "after": true 147 | } 148 | ], 149 | "space-before-blocks": [ 150 | "error", 151 | "always" 152 | ], 153 | "space-before-function-paren": [ 154 | "error", 155 | { 156 | "anonymous": "never", 157 | "named": "never", 158 | "asyncArrow": "always" 159 | } 160 | ], 161 | "space-in-parens": [ 162 | "error", 163 | "never" 164 | ], 165 | "space-infix-ops": [ 166 | "error" 167 | ], 168 | "space-unary-ops": [ 169 | "error", 170 | { 171 | "words": true, 172 | "nonwords": false, 173 | "overrides": { 174 | "typeof": false 175 | } 176 | } 177 | ], 178 | "no-unreachable": [ 179 | "error" 180 | ], 181 | "no-global-assign": [ 182 | "error" 183 | ], 184 | "no-self-compare": [ 185 | "error" 186 | ], 187 | "no-unmodified-loop-condition": [ 188 | "error" 189 | ], 190 | "no-constant-condition": [ 191 | "error", 192 | { 193 | "checkLoops": false 194 | } 195 | ], 196 | "no-console": [ 197 | "off" 198 | ], 199 | "no-useless-concat": [ 200 | "error" 201 | ], 202 | "no-useless-escape": [ 203 | "error" 204 | ], 205 | "no-shadow-restricted-names": [ 206 | "error" 207 | ], 208 | "no-use-before-define": [ 209 | "error", 210 | { 211 | "functions": false 212 | } 213 | ], 214 | "arrow-parens": [ 215 | "error", 216 | "always" 217 | ], 218 | "arrow-body-style": [ 219 | "error", 220 | "as-needed" 221 | ], 222 | "arrow-spacing": [ 223 | "error" 224 | ], 225 | "no-confusing-arrow": [ 226 | "error", 227 | { 228 | "allowParens": true 229 | } 230 | ], 231 | "no-useless-computed-key": [ 232 | "error" 233 | ], 234 | "no-useless-rename": [ 235 | "error" 236 | ], 237 | "no-var": [ 238 | "error" 239 | ], 240 | "object-shorthand": [ 241 | "error", 242 | "always" 243 | ], 244 | "prefer-arrow-callback": [ 245 | "error" 246 | ], 247 | "prefer-const": [ 248 | "error" 249 | ], 250 | "prefer-numeric-literals": [ 251 | "error" 252 | ], 253 | "prefer-rest-params": [ 254 | "error" 255 | ], 256 | "prefer-spread": [ 257 | "error" 258 | ], 259 | "rest-spread-spacing": [ 260 | "error", 261 | "never" 262 | ], 263 | "template-curly-spacing": [ 264 | "error", 265 | "never" 266 | ], 267 | "consistent-return": [ 268 | "error", 269 | { "treatUndefinedAsUnspecified": true } 270 | ] 271 | } 272 | } 273 | --------------------------------------------------------------------------------