├── .gitignore ├── README.md ├── bin └── jsonymize ├── lib └── jsonymize.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jsonymize 2 | Anonymize JSON values, easily. 3 | 4 | ## Installation 5 | ```bash 6 | npm install -g babel jsonymize 7 | ``` 8 | 9 | ## Usage 10 | jsonymize reads data from standard input, anonymizes, then writes to 11 | standard output. By default all fields are anonymized, however, specific field 12 | names can be passed as arguments as shown below. 13 | 14 | Choose fields to anonymize: 15 | ```bash 16 | $ cat input.json 17 | {"name": "Cameron Hunter", "age": 30, "email": "hello@cameronhunter.co.uk"} 18 | $ cat input.json | jsonymize email age 19 | {"name":"Cameron Hunter","age":58,"email":"erib@jinvuaj.net"} 20 | ``` 21 | 22 | Field names can be "fully qualified" using dot-notation: 23 | ```bash 24 | $ cat input.json 25 | {"user":{"name": "Cameron Hunter", "age": 30, "email": "hello@cameronhunter.co.uk"}} 26 | $ cat input.json | jsonymize user.name *.age 27 | {"user":{"name":"Alejandro Mann","age":35,"email":"hello@cameronhunter.co.uk"}} 28 | ``` 29 | 30 | ## Advanced Configuration 31 | A configuration file can be passed to jsonymize, providing advanced control 32 | over the data generators, as well as allowing configurations to be shared. 33 | 34 | Example configuration file: 35 | ```json 36 | { 37 | "aliases": { 38 | "userAge": "user.age" 39 | }, 40 | "fields": ["name", "cell", "userAge"], 41 | "extensions": [ 42 | "../extensions/nickname-extension.js" 43 | ], 44 | "generators": { 45 | "name": "nickname", 46 | "cell": "phone", 47 | "text": { 48 | "generator": "sentence", 49 | "params": { 50 | "words": 10 51 | } 52 | } 53 | } 54 | } 55 | ``` 56 | 57 | ```bash 58 | $ cat input.json 59 | {"name": "Cameron Hunter", "age": 30, "cell": "(939) 555-0113"} 60 | $ cat input.json | jsonymize -c ~/configuration.json 61 | {"name":"Terry 'Hulk' Hogan","age":30,"cell":"(636) 555-3226"} 62 | ``` 63 | 64 | [ChanceJS](https://github.com/victorquinn/chancejs) is used to generate all 65 | randomized data. A full list of supported generators and their options is 66 | available on their [website](http://chancejs.com). 67 | -------------------------------------------------------------------------------- /bin/jsonymize: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env babel-node 2 | 3 | import Jsonymize from "../lib/jsonymize"; 4 | 5 | import CJSON from "cjson"; 6 | import Check from "check-types"; 7 | import Yargs from "yargs"; 8 | 9 | import fs from "fs"; 10 | import path from "path"; 11 | 12 | Yargs 13 | .usage("Anonymize JSON values.\n\nUsage: jsonymize [fields]") 14 | .options("e", { 15 | alias: "extension", 16 | description: "ChanceJS mixin providing custom generators" 17 | }) 18 | .options("c", { 19 | alias: "config", 20 | description: "Advanced configuration file" 21 | }) 22 | .options("h", { 23 | alias: "help", 24 | description: "Show this help message" 25 | }); 26 | 27 | const { argv } = Yargs; 28 | const { stdin, stdout, stderr, exit, cwd } = process; 29 | 30 | stdin.resume(); 31 | stdin.setEncoding("utf8"); 32 | 33 | if (argv.help) { 34 | Yargs.showHelp(); 35 | exit(1); 36 | } 37 | 38 | const configPath = argv.config ? path.resolve(cwd(), argv.config) : undefined; 39 | if (configPath && !fs.existsSync(configPath)) { 40 | stderr.write(`Could not read configuration file "${configPath}"\n`); 41 | exit(2); 42 | } 43 | 44 | const config = argv.config ? CJSON.load(argv.config) : {}; 45 | const generators = argv.generator || config.generators || {}; 46 | const extensions = fallback(argv.extension, relative(argv.config, config.extensions), []); 47 | const aliases = argv.alias || config.aliases || {}; 48 | const fields = (argv._.length ? argv._ : undefined) || config.fields || []; 49 | 50 | const anonymizer = new Jsonymize({ 51 | aliases: aliases, 52 | fields: fields, 53 | generators: generators, 54 | extensions: extensions.map(_ => require(path.resolve(cwd(), _))) 55 | }); 56 | 57 | anonymizer.anonymize(stdin).then(anonymized => { 58 | stdout.write(`${JSON.stringify(anonymized)}\n`); 59 | exit(0); 60 | }).catch(error => { 61 | stderr.write(`Error! ${JSON.stringify(error.thrown)}\n`); 62 | exit(3); 63 | }); 64 | 65 | function fallback(args, config, def) { 66 | if (args) { 67 | return Check.array(args) ? args : [args]; 68 | } else if (config) { 69 | return config 70 | } else { 71 | return def; 72 | } 73 | } 74 | 75 | function relative(configPath, extensions) { 76 | if (!configPath || !extensions) { 77 | return extensions; 78 | } else { 79 | return extensions.map(_ => path.join(path.dirname(configPath), _)); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/jsonymize.js: -------------------------------------------------------------------------------- 1 | import Oboe from "oboe"; 2 | import Check from "check-types"; 3 | import Chance from "chance"; 4 | 5 | const __options__ = Symbol("options"); 6 | const __chance__ = Symbol("chance"); 7 | 8 | export default class Jsonymize { 9 | constructor(options = {}) { 10 | const { aliases = {}, extensions = [], fields = [], generators = {} } = options; 11 | this[__options__] = { aliases: aliases, extensions: extensions, fields: fields, generators: generators }; 12 | this[__chance__] = extensions.reduce((chance, extension) => chance.mixin(extension), new Chance()); 13 | } 14 | 15 | anonymize(stream) { 16 | const chance = this[__chance__]; 17 | const { aliases, fields, generators } = this[__options__]; 18 | 19 | const actions = fields.reduce((result, alias) => { 20 | const field = aliases[alias] || alias; 21 | const isComplexOverride = Check.object(generators[alias]); 22 | const generatorOverride = isComplexOverride ? generators[alias].generator : generators[alias]; 23 | const parameterOverride = isComplexOverride ? generators[alias].params : {}; 24 | 25 | return Object.assign({}, result, { 26 | [field]: (value, path) => { 27 | const generators = findGenerators(chance, generatorOverride, alias, field, ...types(value)); 28 | return generators.map(_ => _.generator(Object.assign({}, parameterOverride, { value: value })))[0]; 29 | } 30 | }); 31 | }, {}); 32 | 33 | return new Promise((resolve, reject) => { 34 | Oboe(stream).node(actions).done(resolve).fail(reject); 35 | }); 36 | } 37 | } 38 | 39 | function findGenerators(chance, ...generators) { 40 | return generators 41 | .filter(value => value != null) 42 | .map(name => ({name: name, generator: chance[name]})) 43 | .filter(({ generator }) => Check.function(generator)) 44 | .map(({ name, generator }) => ({name: name, generator: generator.bind(chance)})); 45 | } 46 | 47 | function types(value) { 48 | return [ 49 | ["natural", Check.number], 50 | ["bool", Check.boolean], 51 | ["string", Check.string], 52 | ["date", Check.date], 53 | ["sentence", (value) => { Check.string(value) && value.includes(" ") }] 54 | ].map(([type, predicate]) => (predicate(value) ? type : undefined)); 55 | } 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsonymize", 3 | "author": "Cameron Hunter (http://www.cameronhunter.co.uk/)", 4 | "version": "2.0.0", 5 | "description": "Anonymize JSON values, easily.", 6 | "license": "MIT", 7 | "main": "lib/jsonymize.js", 8 | "bin": { 9 | "jsonymize": "bin/jsonymize" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "http://github.com/cameronhunter/jsonymize.git" 14 | }, 15 | "dependencies": { 16 | "chance": "~0.7.3", 17 | "check-types": "~3.3.0", 18 | "cjson": "~0.3.1", 19 | "oboe": "~2.1.2", 20 | "yargs": "~1.3.3" 21 | }, 22 | "keywords": [ 23 | "anonymize", 24 | "anonymise", 25 | "json" 26 | ] 27 | } 28 | --------------------------------------------------------------------------------