Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sorting arrays #39

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
The MIT License (MIT)

Copyright (c) 2019 David Björklund

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

90 changes: 75 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,90 @@
sort-json
=========
# sort-json [![Build Status](https://travis-ci.org/kesla/sort-json.svg?branch=master)](https://travis-ci.org/kesla/sort-json)

It takes a JSON file and returns a copy of the same file, but with the sorted keys.

installation
------------
## Installation

` [sudo] npm -g install sort-json`


usage
-----
## Usage

```js
var sortJson = require('sort-json');
const sortJson = require('sort-json');

const options = { ignoreCase: true, reverse: true, depth: 1};
const copy = sortJson({ AA: 123, a: 1, b: 21 }, options);
// copy => { b: 21, AA: 123, a: 1 }

sortJson.overwrite('some/absolute/path.json', options);
// sorts the json at absolute path and overwrites file, also returns sorted object

sortJson.overwrite(['some/absolute/path1.json', 'some/absolute/path2.json'], options);
// sorts the json at absolute paths and overwrites files, also returns array of sorted objects
```

## CLI usage

`sort-json filename [options]`
Sorts and overwrites .json or .rc files.

_Example_
`sort-json test.json --ignore-case`

**Options**

`--ignore-case, -i`\
Ignore case when sorting.

`--reverse, -r`\
Reverse the ordering z -> a

var copy = sortJson(object);
`--depth=DEPTH, -d`\
The sorting _DEPTH_ on multidimensional objects.
Use a number greater then 0 for the _DEPTH_ value.

`--indent-size=SIZE, --spaces=SIZE`\
Formats the file content with an indentation of _SIZE_ spaces (default: detects the used indentation of the file).
Use a number greater then 0 for the _SIZE_ value.

`--no-final-newline, -nn`\
No final new line will be added to the end of the file.

`--key '$._id', -k '$._id'`\
Will sort an array wether it's nested in an object or even in an other array (using mongoDB like operators) :

the Key must be formated this way :

`$` : is an array.\
`.key`: is a properity.

for example:

`$.foo.bar.$` : will sort all the arrays contained in every bar of foo in the main array :

```
[
{
foo: {
bar: [ this array will be sorted ]
}
},
{
foo: {
bar: [ this array will be sorted too ]
}
}
]
```

CLI usage
---------
`sort-json file.json`

For now sort-json takes no other arguments, so the original file will be overwritten by a sorted JSON file, keeping the indentation of the original file.
`--DESC'`\
Reverse the ordering of an array (to use with `--key` option).

## Upgrade to version 2.x

sort-json 2.0.0 will create a different output when the source JSON file does not use an indent size of 2 spaces.
Use `--indent-size=2` to always create an output file with 2 spaces.

tests
-----
## Tests

`npm test`
25 changes: 25 additions & 0 deletions app/cmd.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env node

// Core dependencies
const path = require('path');

// NPM dependencies
const minimist = require('minimist');
const sortJson = require('./');

const alias = {
key: ['key', 'k'],
DESC: ['DESC'],
depth: ['d'],
reverse: ['r'],
ignoreCase: ['ignore-case', 'i'],
indentSize: ['indent-size', 'spaces'],
noFinalNewLine: ['no-final-newline', 'nn'],
};

const argv = minimist(process.argv.slice(2), { alias });

// Get all the files
const files = argv._.filter(arg => arg.endsWith('.json') || arg.endsWith('.rc'));

sortJson.overwrite(files.map(file => path.resolve(file)), argv);
5 changes: 5 additions & 0 deletions app/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const visit = require('./visit');
const overwrite = require('./overwrite');

module.exports = visit;
module.exports.overwrite = overwrite;
73 changes: 73 additions & 0 deletions app/overwrite.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
const fs = require('fs');
const detectIndent = require('detect-indent');
const detectNewline = require('detect-newline');

const visit = require('./visit');
const sortByKey = require('./sortByKey')

const DEFAULT_INDENT_SIZE = 2;

/**
* Overwrite file with sorted json
* @param {String} path - absolutePath
* @param {Object} [options = {}] - optional params
* @returns {*}
*/
function overwriteFile(path, options = {}) {
let fileContent = null;
let newData = null;

try {
fileContent = fs.readFileSync(path, 'utf8');
newData = visit(JSON.parse(fileContent), options);

const key = options.key || null;
const DESC = options.DESC || false;
if (key !== null) {
newData = sortByKey(newData, key, DESC)
}

} catch (e) {
console.error('Failed to retrieve json object from file');
throw e;
}

let indent;

if (options && options.indentSize) {
indent = options.indentSize;
} else {
indent = detectIndent(fileContent).indent || DEFAULT_INDENT_SIZE;
}

const newLine = detectNewline(fileContent) || '\n';
let newFileContent = JSON.stringify(newData, null, indent);

if (!(options && options.noFinalNewLine)) {
// Append a new line at EOF
newFileContent += '\n';
}

if (newLine !== '\n') {
newFileContent = newFileContent.replace(/\n/g, newLine);
}

fs.writeFileSync(path, newFileContent, 'utf8');
return newData;
}

/**
* Sorts the files json with the visit function and then overwrites the file with sorted json
* @see visit
* @param {String|Array} absolutePaths - String: Absolute path to json file to sort and overwrite
* Array: Absolute paths to json files to sort and overwrite
* @param {Object} [options = {}] - Optional parameters object, see visit for details
* @returns {*} - Whatever is returned by visit
*/
function overwrite(absolutePaths, options) {
const paths = Array.isArray(absolutePaths) ? absolutePaths : [absolutePaths];
const results = paths.map(path => overwriteFile(path, options));
return results.length > 1 ? results : results[0];
}

module.exports = overwrite;
61 changes: 61 additions & 0 deletions app/sortByKey.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
function getKeyValue(key, obj) {
const keyParts = key.split('.').filter(k => k.length);
return keyParts.reduce((r, i) => {
if (r[i] == null) {
r[i] = {};
}
return r[i];
}, obj);
}

function asc_cmp(a, b) {
const type = typeof(a);
if (type === 'string') {
return a.localeCompare(b);
} else {
return a - b;
}
}

function desc_cmp(a, b) {
return asc_cmp(a, b) * -1;
}

function sortArray(arr, key, DESC) {
const cmp = DESC ? desc_cmp : asc_cmp;
return arr.sort((a, b) => {
const values = [
getKeyValue(key, a),
getKeyValue(key, b)
];
return cmp(values[0], values[1]);
})
}

function sortbyKey(obj, key, DESC) {
const arraysToSort = key.split('$.').filter(k => k.length);

if (arraysToSort.length === 0) {
const cmp = DESC ? desc_cmp : asc_cmp;
return obj.sort(cmp);
}
if (arraysToSort.length === 1) {
return sortArray(obj, arraysToSort[0], DESC);
}

const isArray = Array.isArray(obj);
const nextKey = '$.' + arraysToSort.slice(1).join('$.');

if (isArray) {
obj.forEach((_obj) => {
let child = getKeyValue(arraysToSort[0], _obj);
child = sortbyKey(child, nextKey, DESC);
});
} else {
let child = getKeyValue(arraysToSort[0], obj);
child = sortbyKey(child, nextKey, DESC);
}
return obj;
}

module.exports = sortbyKey;
51 changes: 51 additions & 0 deletions app/visit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* Sorts the keys on objects
* @param {*} old - An object to sort the keys of, if not object just
* returns whatever was given
* @param {Object} [sortOptions = {}] - optional parameters
* @param [options.reverse = false] - When sorting keys, converts all keys to lowercase so
* that capitalization doesn't interfere with sort order
* @param [options.ignoreCase = false] - When sorting keys, converts all keys to
* @param [options.depth = Infinity] - Depth's level sorting keys on a
* multidimensional object
* @param [options.key] - it will sort arrays by the mentionned key (nested keys are separated by a ".", arrays by '$')
* @param [options.DESC] - to use with the key options: sort arrays by descending values instead of ascending
*
* @returns {*} - Object with sorted keys, if old wasn't an object
* returns whatever was passed
*/
function visit(old, options) {
const sortOptions = options || {};

const ignoreCase = sortOptions.ignoreCase || false;
const reverse = sortOptions.reverse || false;
const depth = sortOptions.depth || Infinity;
const level = sortOptions.level || 1;
const processing = level <= depth;

if (typeof (old) !== 'object' || old === null) {
return old;
}

const copy = Array.isArray(old) ? [] : {};
let keys = Object.keys(old);
if (processing) {
keys = ignoreCase ?
keys.sort((left, right) => left.toLowerCase().localeCompare(right.toLowerCase())) :
keys.sort();
}

if (reverse) {
keys = keys.reverse();
}

keys.forEach((key) => {
const subSortOptions = Object.assign({}, sortOptions);
subSortOptions.level = level + 1;
copy[key] = visit(old[key], subSortOptions);
});

return copy;
}

module.exports = visit;
19 changes: 17 additions & 2 deletions cmd.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,22 @@ var detectIndent = require('detect-indent');
var sortJson = require('./');

// Get all the files
var files = process.argv.slice(2);
var files = process.argv.splice(2, 3);
var key = null;

for (var x = 0; x < files.length; x++){
if (files[x] === "-k" || files[x] === '--key'){
if (files.length !== x + 2){
console.log("error: no key was given, ignoring the flag");
files.pop();
break;
} else {
key = files.pop();
files.pop();
break;
}
}
}

files.forEach(readEachFile);

Expand Down Expand Up @@ -37,7 +52,7 @@ function readEachFile(fileName) {
}

// Sorting
var sortedObject = sortJson(json);
var sortedObject = sortJson(json, key);

// Saving to file
fs.writeFile(filePath, JSON.stringify(sortedObject, null, indent) + ((eol && eol.length === 2) ? eol[1] : ''), function(err) {
Expand Down
14 changes: 2 additions & 12 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,3 @@
module.exports = visit;
'use strict';

function visit(old) {
if (typeof(old) !== 'object' || old === null) {
return old;
}
var copy = Array.isArray(old) ? [] : {};
var keys = Object.keys(old).sort();
keys.forEach(function(key) {
copy[key] = visit(old[key]);
});
return copy;
}
module.exports = require('./app');
Loading