├── test ├── .gitignore ├── test.json ├── sub │ └── test2.json ├── test.coffee └── test.js ├── .gitignore ├── package.json ├── LICENSE ├── src └── json-update.coffee ├── README.md └── lib └── json-update.js /test/.gitignore: -------------------------------------------------------------------------------- 1 | sub 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /test/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "test1": "hello", 3 | "test2": 2 4 | } 5 | -------------------------------------------------------------------------------- /test/sub/test2.json: -------------------------------------------------------------------------------- 1 | { 2 | "test": "new", 3 | "nest": { 4 | "a": 10, 5 | "b": 20 6 | }, 7 | "promise": true 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json-update", 3 | "version": "5.3.0", 4 | "description": "Allows for very simple JSON file updating in one line", 5 | "author": "Jason Livesay", 6 | "main": "lib/json-update.js", 7 | "keywords": [ 8 | "json", 9 | "update", 10 | "serialization", 11 | "deserialization", 12 | "save", 13 | "load", 14 | "file", 15 | "merge", 16 | "database" 17 | ], 18 | "dependencies": { 19 | "deep-extend": "^0.6.0", 20 | "fs-extra": "", 21 | "lockfile": "~1.0.4", 22 | "pify": "^4.0.1", 23 | "underscore": "" 24 | }, 25 | "scripts": {}, 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/ithkuil/json-update.git" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2014 Jason Livesay 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/json-update.coffee: -------------------------------------------------------------------------------- 1 | #This is actually ToffeeScript https://github.com/jiangmiao/toffee-script 2 | 3 | fs = require 'fs-extra' 4 | lockfile= require 'lockfile' 5 | und = require 'underscore' 6 | deepExtend = require 'deep-extend' 7 | pify = require 'pify' 8 | 9 | opts = 10 | wait: 2000 11 | stale: 30000 12 | retries: 2 13 | retryWait: 100 14 | 15 | cfg = 16 | deep: false 17 | 18 | exports.config = (conf) -> 19 | cfg = und.extend cfg, conf 20 | 21 | fixempty = (cb) -> 22 | if not cb? 23 | return ( -> ) 24 | else 25 | return cb 26 | 27 | doload = (filename, unlock, cb) -> 28 | cb = fixempty cb 29 | er = lockfile.lock! "#{filename}.lock", opts 30 | if er? 31 | return cb er 32 | else 33 | err, data = fs.readFile! filename, 'utf8' 34 | if unlock then lockfile.unlock! "#{filename}.lock" 35 | if err? then return cb new Error("Error reading JSON file #{filename}: #{err?.message}") 36 | try 37 | return cb null, JSON.parse data 38 | catch e 39 | return cb new Error("Error parsing JSON in #{filename}. Data is #{data}. Error was #{e.message}") 40 | 41 | load = (filename, cb) -> 42 | doload filename, true, cb 43 | 44 | update = (filename, obj, cb) -> 45 | cb = fixempty cb 46 | loaded = (data) -> 47 | if not cfg.deep 48 | data = und.extend data, obj 49 | else 50 | deepExtend data, obj 51 | 52 | err = fs.outputJson! filename, data 53 | lockfile.unlock! "#{filename}.lock" 54 | if err? then return cb new Error("Problem saving JSON file: #{err?.message}") 55 | return cb null, data 56 | 57 | if not fs.exists!(filename) 58 | loaded {}, false 59 | else 60 | err, filedata = doload! filename, false 61 | loaded filedata, false 62 | 63 | exports.load = pify load 64 | exports.update = pify update 65 | 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a simple way to update (or load) a JSON file. If the JSON file does not exist it will be created (along with a directory structure if those directories don't exist yet). 2 | 3 | Uses underscore to extend existing JSON data with the object you specify, overriding anything with an 4 | existing property and adding properties if they are new. 5 | 6 | NEW: You may change the extend behavior by calling `config({deep:true})` before calling `update`. This will use `deep-extend` instead of 7 | underscore extend to merge the data when you call `update`. 8 | 9 | New version supports promises (and async/await with babel). 10 | 11 | `npm install json-update` 12 | 13 | Updating a JSON file (and return the new object) (with promise): 14 | 15 | ```javascript 16 | json = require('json-update'); 17 | 18 | json.update('data.json',{test:10}) 19 | .then(function(dat) { 20 | console.log(dat.test) 21 | }); 22 | 23 | ``` 24 | 25 | With async/await (you must use `babel` with `babel-polyfill` etc. as with all use of async/await): 26 | 27 | ```javascript 28 | import {update, load} from 'json-update'; 29 | 30 | async function test() { 31 | await update('t.json', {x:2}); 32 | let dat = await load('t.json'); 33 | console.log(dat.x); 34 | } 35 | 36 | test().then(()=> {}).catch( e=> {console.error(e)}); 37 | ``` 38 | [](https://www.yang2020.com/) 39 | 40 | 41 | 42 | 43 | With a callback: 44 | 45 | ```javascript 46 | json = require('json-update') 47 | 48 | json.update('data.json', { test: 'value x' }, function(err, obj) { 49 | if (typeof err !== "undefined" && err !== null) { 50 | console.log("Error updating json: " + err.message); 51 | } 52 | console.log(obj); 53 | }); 54 | ``` 55 | 56 | Loading a JSON file (note that in the case of loading a valid JSON file must already exist): 57 | 58 | ```javascript 59 | json = require('json-update') 60 | 61 | json.load('data.json', function(err, obj) { 62 | console.log("Loaded from json:"); 63 | console.log(obj); 64 | }); 65 | 66 | ``` 67 | 68 | -------------------------------------------------------------------------------- /test/test.coffee: -------------------------------------------------------------------------------- 1 | assert = require 'assert' 2 | fs = require 'fs' 3 | json = require '../lib/json-update.js' 4 | 5 | describe 'json-update', -> 6 | describe '#load()', -> 7 | it 'should read a JSON file into an object', (done) -> 8 | json.load 'test.json', (err, obj) -> 9 | console.log err 10 | assert.equal obj.test1, 'hello' 11 | assert.equal obj.test2, 2 12 | done() 13 | 14 | describe 'update non-existing', -> 15 | it 'should create a subdirectory and JSON file with data', (done) -> 16 | if fs.existsSync 'sub' 17 | fs.unlinkSync 'sub/test2.json' 18 | fs.rmdirSync 'sub' 19 | json.update 'sub/test2.json', { test: 'val', nest: { a: 10 } }, (err) -> 20 | assert.equal err, null 21 | fs.exists 'sub/test2.json', (exists) -> 22 | assert.equal exists, true 23 | fs.readFile 'sub/test2.json', 'utf8', (err, str) -> 24 | assert.equal null, err 25 | dat = JSON.parse str 26 | assert.equal dat.test, 'val' 27 | done() 28 | 29 | describe 'update existing', -> 30 | it 'should update existing JSON file with data ..', (done) -> 31 | json.update 'sub/test2.json', { test: 'new' }, (err, data) -> 32 | assert.equal err, null 33 | assert.equal data.test, 'new' 34 | fs.readFile 'sub/test2.json', 'utf8', (err, str) -> 35 | read = JSON.parse str 36 | assert.equal read.test, 'new' 37 | done() 38 | 39 | describe 'update deep existing', -> 40 | it 'should deep extend existing JSON file with data ..', (done) -> 41 | json.config {deep:true} 42 | json.update 'sub/test2.json', { nest: {b:20} }, (err, data) -> 43 | assert.equal err, null 44 | assert.equal data.test, 'new' 45 | assert.equal data.nest.a, 10 46 | assert.equal data.nest.b, 20 47 | 48 | fs.readFile 'sub/test2.json', 'utf8', (err, str) -> 49 | read = JSON.parse str 50 | assert.equal read.test, 'new' 51 | assert.equal data.nest.b, 20 52 | done() 53 | 54 | describe 'try with promise', -> 55 | it 'should work with a promise', (done) -> 56 | json.update('sub/test2.json', { promise: true }) 57 | .then (data) -> 58 | assert.equal(data.promise, true); 59 | done(); 60 | 61 | 62 | -------------------------------------------------------------------------------- /lib/json-update.js: -------------------------------------------------------------------------------- 1 | // Generated by ToffeeScript 1.6.3-4 2 | (function() { 3 | var cfg, deepExtend, fs, lockfile, opts, pify, und; 4 | 5 | fs = require('fs-extra'); 6 | 7 | lockfile = require('lockfile'); 8 | 9 | und = require('underscore'); 10 | 11 | deepExtend = require('deep-extend'); 12 | 13 | pify = require('pify'); 14 | 15 | opts = { 16 | wait: 2000, 17 | stale: 30000, 18 | retries: 2, 19 | retryWait: 100 20 | }; 21 | 22 | cfg = { 23 | deep: false 24 | }; 25 | 26 | exports.config = function(conf) { 27 | return cfg = und.extend(cfg, conf); 28 | }; 29 | 30 | function fixempty(cb) { 31 | if (cb == null) { 32 | return (function() {}); 33 | } else { 34 | return cb; 35 | } 36 | }; 37 | 38 | function doload(filename, unlock, cb) { 39 | var data, e, er, err, 40 | _this = this; 41 | cb = fixempty(cb); 42 | lockfile.lock("" + filename + ".lock", opts, function() { 43 | er = arguments[0]; 44 | if (er != null) { 45 | return cb(er); 46 | } else { 47 | fs.readFile(filename, 'utf8', function() { 48 | err = arguments[0], data = arguments[1]; 49 | if (unlock) { 50 | lockfile.unlock("" + filename + ".lock", function() { 51 | _$$_0(); 52 | }); 53 | } else { 54 | _$$_0(); 55 | } 56 | function _$$_0() { 57 | if (err != null) { 58 | return cb(new Error("Error reading JSON file " + filename + ": " + (err != null ? err.message : void 0))); 59 | } 60 | try { 61 | return cb(null, JSON.parse(data)); 62 | } catch (_error) { 63 | e = _error; 64 | return cb(new Error("Error parsing JSON in " + filename + ". Data is " + data + ". Error was " + e.message)); 65 | } 66 | }; 67 | }); 68 | } 69 | }); 70 | }; 71 | 72 | function load(filename, cb) { 73 | return doload(filename, true, cb); 74 | }; 75 | 76 | function update(filename, obj, cb) { 77 | var err, filedata, 78 | _this = this; 79 | cb = fixempty(cb); 80 | function loaded(data) { 81 | var err, 82 | _this = this; 83 | if (!cfg.deep) { 84 | data = und.extend(data, obj); 85 | } else { 86 | deepExtend(data, obj); 87 | } 88 | fs.outputJson(filename, data, function() { 89 | err = arguments[0]; 90 | lockfile.unlock("" + filename + ".lock", function() { 91 | if (err != null) { 92 | return cb(new Error("Problem saving JSON file: " + (err != null ? err.message : void 0))); 93 | } 94 | return cb(null, data); 95 | }); 96 | }); 97 | }; 98 | fs.exists(filename, function(_$$_3) { 99 | _$cb$_2(!_$$_3); 100 | }); 101 | function _$cb$_2(_$$_1) { 102 | if (_$$_1) { 103 | return loaded({}, false); 104 | } else { 105 | doload(filename, false, function() { 106 | err = arguments[0], filedata = arguments[1]; 107 | return loaded(filedata, false); 108 | }); 109 | } 110 | }; 111 | }; 112 | 113 | exports.load = pify(load); 114 | 115 | exports.update = pify(update); 116 | 117 | }).call(this); 118 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | (function() { 3 | var assert, fs, json; 4 | 5 | assert = require('assert'); 6 | 7 | fs = require('fs'); 8 | 9 | json = require('../lib/json-update.js'); 10 | 11 | describe('json-update', function() { 12 | describe('#load()', function() { 13 | return it('should read a JSON file into an object', function(done) { 14 | return json.load('test.json', function(err, obj) { 15 | console.log(err); 16 | assert.equal(obj.test1, 'hello'); 17 | assert.equal(obj.test2, 2); 18 | return done(); 19 | }); 20 | }); 21 | }); 22 | describe('update non-existing', function() { 23 | return it('should create a subdirectory and JSON file with data', function(done) { 24 | if (fs.existsSync('sub')) { 25 | fs.unlinkSync('sub/test2.json'); 26 | fs.rmdirSync('sub'); 27 | } 28 | return json.update('sub/test2.json', { 29 | test: 'val', 30 | nest: { 31 | a: 10 32 | } 33 | }, function(err) { 34 | assert.equal(err, null); 35 | return fs.exists('sub/test2.json', function(exists) { 36 | assert.equal(exists, true); 37 | return fs.readFile('sub/test2.json', 'utf8', function(err, str) { 38 | var dat; 39 | assert.equal(null, err); 40 | dat = JSON.parse(str); 41 | assert.equal(dat.test, 'val'); 42 | return done(); 43 | }); 44 | }); 45 | }); 46 | }); 47 | }); 48 | describe('update existing', function() { 49 | return it('should update existing JSON file with data ..', function(done) { 50 | return json.update('sub/test2.json', { 51 | test: 'new' 52 | }, function(err, data) { 53 | assert.equal(err, null); 54 | assert.equal(data.test, 'new'); 55 | return fs.readFile('sub/test2.json', 'utf8', function(err, str) { 56 | var read; 57 | read = JSON.parse(str); 58 | assert.equal(read.test, 'new'); 59 | return done(); 60 | }); 61 | }); 62 | }); 63 | }); 64 | describe('update deep existing', function() { 65 | return it('should deep extend existing JSON file with data ..', function(done) { 66 | json.config({ 67 | deep: true 68 | }); 69 | return json.update('sub/test2.json', { 70 | nest: { 71 | b: 20 72 | } 73 | }, function(err, data) { 74 | assert.equal(err, null); 75 | assert.equal(data.test, 'new'); 76 | assert.equal(data.nest.a, 10); 77 | assert.equal(data.nest.b, 20); 78 | return fs.readFile('sub/test2.json', 'utf8', function(err, str) { 79 | var read; 80 | read = JSON.parse(str); 81 | assert.equal(read.test, 'new'); 82 | assert.equal(data.nest.b, 20); 83 | return done(); 84 | }); 85 | }); 86 | }); 87 | }); 88 | return describe('try with promise', function() { 89 | return it('should work with a promise', function(done) { 90 | return json.update('sub/test2.json', { 91 | promise: true 92 | }).then(function(data) { 93 | assert.equal(data.promise, true); 94 | return done(); 95 | }); 96 | }); 97 | }); 98 | }); 99 | 100 | }).call(this); 101 | --------------------------------------------------------------------------------