├── LICENSE.md ├── Readme.md ├── build ├── checker-browser.js ├── checker.js └── parser.js ├── gulpfile.coffee ├── package.json ├── src ├── checker.coffee └── parser.coffee └── test ├── mocha.opts ├── test_checker.coffee └── test_parser.coffee /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Benjamin J. Steephenson 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 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Deep Type 2 | ### JS GraphQL-ish Type Checker 3 | 4 | Use a GraphQL-esque syntax to validate Javascript objects and arrays. Also tells you where you messed up. 5 | 6 | ``` 7 | npm install deep-type 8 | ``` 9 | 10 | ## Examples 11 | 12 | ``` 13 | checker = require './checker.coffee' 14 | 15 | objectToValidate = { 16 | key1: { 17 | innerKey: 'value' 18 | } 19 | } 20 | 21 | checker('{key1: {innerKey}}', objectToValidate) 22 | => {isValid: true, stack: []} 23 | 24 | checker('{key1: {innerKey: number}}', objectToValidate) 25 | => {isValid: false, stack: ['key1', 'innerKey']} 26 | 27 | arrayToValidate = [ 28 | { 29 | someKey: 'val' 30 | another: 'val' 31 | } 32 | { 33 | someKey: 'val' 34 | } 35 | ] 36 | 37 | checker('[ {someKey, another} ]', objectToValidate) 38 | => {isValid: false, stack: [0, 'another']} 39 | 40 | ``` 41 | 42 | ## Usage 43 | 44 | checker(query, objectOrArrayToValidate) returns an object that looks like 45 | { 46 | isValid: true of false 47 | stack: a list of keys/indices 48 | } 49 | 50 | Say the part that failed was object[0]['key']['innerKey'], then stack would be [0, 'key', 'innerKey'] 51 | 52 | If you only provide the query, you get back a partially applied function. 53 | 54 | query = checker('{key}') 55 | query({'key': 'value'}) 56 | => { isValid: true, stack: [ ] } 57 | 58 | ## Writing Queries 59 | 60 | ``` 61 | 62 | someType => the input is of type 'someType' 63 | 64 | {} => the input is an object 65 | 66 | {key1, key2, key3} => the input has the following keys: key1, key2, key3 67 | 68 | {key1, key2:{innerKey}} => input['key2'] has the following keys: innerKey 69 | 70 | { firstKey: string } => input['firstKey'] is a string 71 | 72 | [] => the input is an arrays 73 | 74 | [ innerQuery ] => each of the array's elements matches the innerQuery 75 | 76 | [ firstQuery, secondQuery ] => the array has exactly two elements. the first element matches firstQuery. the second element matches secondQuery 77 | 78 | [ {someKey} ] => each element of the array is an object that has the key 'someKey' 79 | 80 | [ string, number ] => the first element of the array is a string. the second element is a number 81 | 82 | ``` 83 | 84 | 85 | -------------------------------------------------------------------------------- /build/checker-browser.js: -------------------------------------------------------------------------------- 1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 0) { 207 | return 'pair'; 208 | } 209 | return 'string'; 210 | }; 211 | 212 | shallowParsePairString = function(input) { 213 | var firstPart, index, secondPart; 214 | input = input.trim(); 215 | index = input.indexOf(':'); 216 | firstPart = input.substr(0, index).trim(); 217 | secondPart = input.substr(index + 1).trim(); 218 | return { 219 | type: 'pair', 220 | key: firstPart, 221 | value: secondPart 222 | }; 223 | }; 224 | 225 | deepParsePairString = function(input) { 226 | var node; 227 | node = shallowParsePairString(input); 228 | node.value = parse(node.value); 229 | return node; 230 | }; 231 | 232 | deepParseObjectString = function(input) { 233 | var i, index, len, node, ref, value; 234 | node = shallowParseObjectString(input); 235 | ref = node.value; 236 | for (index = i = 0, len = ref.length; i < len; index = ++i) { 237 | value = ref[index]; 238 | node.value[index] = parse(value); 239 | } 240 | return node; 241 | }; 242 | 243 | parse = function(input) { 244 | var type; 245 | type = determineType(input); 246 | if (type === 'object') { 247 | return deepParseObjectString(input); 248 | } 249 | if (type === 'string') { 250 | return stringToNode(input); 251 | } 252 | if (type === 'pair') { 253 | return deepParsePairString(input); 254 | } 255 | if (type === 'array') { 256 | return deepParseArrayString(input); 257 | } 258 | }; 259 | 260 | module.exports = parse; 261 | 262 | 263 | 264 | },{}]},{},[1]) -------------------------------------------------------------------------------- /build/checker.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var isArray, isObject, parser, stack, validate, validateArray, validateObject, validateType; 3 | 4 | parser = require('./parser'); 5 | 6 | stack = []; 7 | 8 | validateObject = function(map, node) { 9 | var child, childNodes, i, len; 10 | childNodes = node.value; 11 | for (i = 0, len = childNodes.length; i < len; i++) { 12 | child = childNodes[i]; 13 | if (child.type === 'string') { 14 | stack.push(child.value); 15 | if (map[child.value] === void 0) { 16 | return false; 17 | } else { 18 | stack.pop(); 19 | } 20 | } else if (child.type === 'pair') { 21 | stack.push(child.key); 22 | if (map[child.key] === void 0) { 23 | return false; 24 | } else { 25 | stack.pop(); 26 | } 27 | stack.push(child.key); 28 | if (validate(map[child.key], child.value) !== true) { 29 | return false; 30 | } else { 31 | stack.pop(); 32 | } 33 | } 34 | } 35 | return true; 36 | }; 37 | 38 | validateArray = function(array, node) { 39 | var children, element, i, index, j, len, len1; 40 | children = node.value; 41 | if (children.length === 0) { 42 | return true; 43 | } else if (children.length === 1) { 44 | for (index = i = 0, len = array.length; i < len; index = ++i) { 45 | element = array[index]; 46 | stack.push(index); 47 | if (validate(element, children[0]) !== true) { 48 | return false; 49 | } else { 50 | stack.pop(); 51 | } 52 | } 53 | return true; 54 | } else { 55 | if (array.length !== children.length) { 56 | return false; 57 | } 58 | for (index = j = 0, len1 = array.length; j < len1; index = ++j) { 59 | element = array[index]; 60 | stack.push(index); 61 | if (validate(element, children[index]) !== true) { 62 | return false; 63 | } else { 64 | stack.pop(); 65 | } 66 | } 67 | return true; 68 | } 69 | }; 70 | 71 | validateType = function(input, node) { 72 | return typeof input === node.value; 73 | }; 74 | 75 | isObject = function(input) { 76 | return input instanceof Object && !(input instanceof Array); 77 | }; 78 | 79 | isArray = function(input) { 80 | return input instanceof Array; 81 | }; 82 | 83 | validate = function(input, node) { 84 | if (node.type === 'object' && isObject(input)) { 85 | return validateObject(input, node); 86 | } else if (node.type === 'array' && isArray(input)) { 87 | return validateArray(input, node); 88 | } else if (node.type === 'string') { 89 | return validateType(input, node); 90 | } else { 91 | return false; 92 | } 93 | }; 94 | 95 | module.exports = function(query, input) { 96 | stack = []; 97 | if (input && query) { 98 | return { 99 | isValid: validate(input, parser(query)), 100 | stack: stack 101 | }; 102 | } else if (query) { 103 | return function(input) { 104 | return { 105 | isValid: validate(input, parser(query)), 106 | stack: stack 107 | }; 108 | }; 109 | } 110 | }; 111 | 112 | }).call(this); 113 | -------------------------------------------------------------------------------- /build/parser.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var deepParseArrayString, deepParseObjectString, deepParsePairString, determineType, parse, shallowParseArrayString, shallowParseObjectString, shallowParsePairString, splitByComma, stringToNode; 3 | 4 | stringToNode = function(input) { 5 | return { 6 | type: 'string', 7 | value: input 8 | }; 9 | }; 10 | 11 | splitByComma = function(input) { 12 | var char, depth, index, lastIndex, val, values; 13 | values = []; 14 | lastIndex = 0; 15 | index = 0; 16 | depth = 0; 17 | input = input.slice(1, -1); 18 | while (index < input.length) { 19 | char = input.charAt(index); 20 | switch (char) { 21 | case '{': 22 | depth++; 23 | break; 24 | case '}': 25 | depth--; 26 | break; 27 | case '[': 28 | depth++; 29 | break; 30 | case ']': 31 | depth--; 32 | } 33 | if (char === ',' && depth === 0) { 34 | values.push(input.substring(lastIndex, index).trim()); 35 | lastIndex = index + 1; 36 | } 37 | index++; 38 | } 39 | if (char !== ',') { 40 | val = input.substring(lastIndex, index).trim(); 41 | if (val !== '') { 42 | values.push(val); 43 | } 44 | } 45 | return values; 46 | }; 47 | 48 | shallowParseObjectString = function(input) { 49 | var values; 50 | input = input.trim(); 51 | values = splitByComma(input); 52 | return { 53 | type: 'object', 54 | value: values 55 | }; 56 | }; 57 | 58 | shallowParseArrayString = function(input) { 59 | var values; 60 | input = input.trim(); 61 | values = splitByComma(input); 62 | return { 63 | type: 'array', 64 | value: values 65 | }; 66 | }; 67 | 68 | deepParseArrayString = function(input) { 69 | var i, index, len, node, ref, value; 70 | node = shallowParseArrayString(input); 71 | ref = node.value; 72 | for (index = i = 0, len = ref.length; i < len; index = ++i) { 73 | value = ref[index]; 74 | node.value[index] = parse(value); 75 | } 76 | return node; 77 | }; 78 | 79 | determineType = function(input) { 80 | var firstChar, lastChar; 81 | input = input.trim(); 82 | if (input.length === 1) { 83 | return 'string'; 84 | } 85 | firstChar = input[0]; 86 | lastChar = input[input.length - 1]; 87 | if (firstChar === '{' && lastChar === '}') { 88 | return 'object'; 89 | } 90 | if (firstChar === '[' && lastChar === ']') { 91 | return 'array'; 92 | } 93 | if (input.indexOf(':') >= 0) { 94 | return 'pair'; 95 | } 96 | return 'string'; 97 | }; 98 | 99 | shallowParsePairString = function(input) { 100 | var firstPart, index, secondPart; 101 | input = input.trim(); 102 | index = input.indexOf(':'); 103 | firstPart = input.substr(0, index).trim(); 104 | secondPart = input.substr(index + 1).trim(); 105 | return { 106 | type: 'pair', 107 | key: firstPart, 108 | value: secondPart 109 | }; 110 | }; 111 | 112 | deepParsePairString = function(input) { 113 | var node; 114 | node = shallowParsePairString(input); 115 | node.value = parse(node.value); 116 | return node; 117 | }; 118 | 119 | deepParseObjectString = function(input) { 120 | var i, index, len, node, ref, value; 121 | node = shallowParseObjectString(input); 122 | ref = node.value; 123 | for (index = i = 0, len = ref.length; i < len; index = ++i) { 124 | value = ref[index]; 125 | node.value[index] = parse(value); 126 | } 127 | return node; 128 | }; 129 | 130 | parse = function(input) { 131 | var type; 132 | type = determineType(input); 133 | if (type === 'object') { 134 | return deepParseObjectString(input); 135 | } 136 | if (type === 'string') { 137 | return stringToNode(input); 138 | } 139 | if (type === 'pair') { 140 | return deepParsePairString(input); 141 | } 142 | if (type === 'array') { 143 | return deepParseArrayString(input); 144 | } 145 | }; 146 | 147 | module.exports = parse; 148 | 149 | }).call(this); 150 | -------------------------------------------------------------------------------- /gulpfile.coffee: -------------------------------------------------------------------------------- 1 | 2 | gulp = require 'gulp' 3 | coffee = require 'gulp-coffee' 4 | browserify = require 'gulp-browserify' 5 | coffeeify = require 'coffeeify' 6 | rename = require 'gulp-rename' 7 | 8 | gulp.task 'coffee', () -> 9 | 10 | gulp.src('./src/*.coffee') 11 | .pipe(coffee({join: true})) 12 | .pipe(gulp.dest('./build/')) 13 | 14 | gulp.task 'browser', () -> 15 | 16 | gulp.src('./src/checker.coffee', {read: false}) 17 | .pipe(browserify({ extensions: ['.coffee'], transform: [coffeeify] })) 18 | .pipe(rename('checker-browser.js')) 19 | .pipe(gulp.dest('./build/')) 20 | 21 | gulp.task 'default', ['coffee', 'browser'] -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deep-type", 3 | "version": "1.0.2", 4 | "description": "a type validator for objects and arrays using GraphQL-ish queries", 5 | "main": "build/checker.js", 6 | "scripts": { 7 | "test": "mocha", 8 | "prepublish": "npm run compile", 9 | "compile": "gulp" 10 | }, 11 | "author": "Benjamin Steephenson", 12 | "license": "MIT", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/BSteephenson/Deep-Type.git" 16 | }, 17 | "devDependencies": { 18 | "chai": "^3.2.0", 19 | "coffee-script": "^1.9.3", 20 | "coffeeify": "^1.1.0", 21 | "gulp": "^3.9.0", 22 | "gulp-browserify": "^0.5.1", 23 | "gulp-coffee": "^2.3.1", 24 | "gulp-rename": "^1.2.2" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/checker.coffee: -------------------------------------------------------------------------------- 1 | 2 | parser = require './parser' 3 | 4 | stack = [] 5 | 6 | validateObject = (map, node) -> 7 | childNodes = node.value 8 | for child in childNodes 9 | if child.type is 'string' 10 | 11 | # check if map has this child 12 | stack.push(child.value) 13 | if map[child.value] is undefined 14 | return false 15 | else 16 | stack.pop() 17 | 18 | else if child.type is 'pair' 19 | 20 | # check if map has this child 21 | stack.push(child.key) 22 | if map[child.key] is undefined 23 | return false 24 | else stack.pop() 25 | 26 | # check if value of this key is valid 27 | stack.push(child.key) 28 | if validate(map[child.key], child.value) isnt true 29 | return false 30 | else 31 | stack.pop() 32 | return true 33 | 34 | 35 | validateArray = (array, node) -> 36 | children = node.value 37 | 38 | if children.length is 0 39 | return true 40 | 41 | else if children.length is 1 42 | # check if every element of the array matches the node 43 | for element, index in array 44 | stack.push(index) 45 | if validate(element, children[0]) isnt true 46 | return false 47 | else 48 | stack.pop() 49 | 50 | return true 51 | 52 | else 53 | # in this case (children.length > 1), the array must exactly match children 54 | if array.length isnt children.length 55 | return false 56 | for element, index in array 57 | stack.push(index) 58 | if validate(element, children[index]) isnt true 59 | return false 60 | else 61 | stack.pop() 62 | 63 | return true 64 | 65 | validateType = (input, node) -> 66 | return typeof input is node.value 67 | 68 | isObject = (input) -> (input instanceof Object and !(input instanceof Array)) 69 | isArray = (input) -> (input instanceof Array) 70 | 71 | validate = (input, node) -> 72 | if node.type is 'object' and isObject(input) 73 | return validateObject(input, node) 74 | else if node.type is 'array' and isArray(input) 75 | return validateArray(input, node) 76 | else if node.type is 'string' 77 | return validateType(input, node) 78 | else 79 | return false 80 | 81 | module.exports = (query, input) -> 82 | stack = [] 83 | 84 | if input && query 85 | return { 86 | isValid: validate(input, parser(query)) 87 | stack: stack 88 | } 89 | else if query 90 | return (input) -> 91 | return { 92 | isValid: validate(input, parser(query)) 93 | stack: stack 94 | } -------------------------------------------------------------------------------- /src/parser.coffee: -------------------------------------------------------------------------------- 1 | 2 | 3 | stringToNode = (input) -> { 4 | type: 'string' 5 | value: input 6 | } 7 | 8 | # splits by comma but keeps nested objects/arrays together 9 | # deletes last element if empty (to allow trailing commas) 10 | # returns an array of strings 11 | splitByComma = (input) -> 12 | values = [] 13 | lastIndex = 0 14 | index = 0 15 | depth = 0 16 | 17 | input = input.slice(1, -1) 18 | 19 | while(index < input.length) 20 | char = input.charAt(index) 21 | switch char 22 | when '{' 23 | depth++ 24 | when '}' 25 | depth-- 26 | when '[' 27 | depth++ 28 | when ']' 29 | depth-- 30 | if char is ',' and depth is 0 31 | values.push(input.substring(lastIndex, index).trim()) 32 | lastIndex = index + 1 33 | index++ 34 | # add the last one 35 | if char isnt ',' 36 | val = input.substring(lastIndex, index).trim() 37 | if val isnt '' 38 | values.push(val) 39 | 40 | return values 41 | 42 | 43 | # turns '{ key1, key2:{blah} }' into {type: 'object', value:['key1', 'key2:{blah}']} 44 | shallowParseObjectString = (input) -> 45 | input = input.trim() 46 | # strip first and last chars 47 | 48 | values = splitByComma(input) 49 | 50 | return { 51 | type: 'object' 52 | value: values 53 | } 54 | 55 | shallowParseArrayString = (input) -> 56 | input = input.trim() 57 | # strip first and last chars 58 | 59 | values = splitByComma(input) 60 | 61 | return { 62 | type: 'array' 63 | value: values 64 | } 65 | 66 | 67 | deepParseArrayString = (input) -> 68 | node = shallowParseArrayString(input) 69 | for value, index in node.value 70 | node.value[index] = parse(value) 71 | return node 72 | 73 | # returns 'object', 'array', or 'string' 74 | determineType = (input) -> 75 | input = input.trim() 76 | if input.length is 1 77 | return 'string' 78 | firstChar = input[0] 79 | lastChar = input[input.length - 1] 80 | if firstChar is '{' and lastChar is '}' 81 | return 'object' 82 | if firstChar is '[' and lastChar is ']' 83 | return 'array' 84 | if input.indexOf(':') >= 0 85 | return 'pair' 86 | return 'string' 87 | 88 | shallowParsePairString = (input) -> 89 | input = input.trim() 90 | index = input.indexOf(':') 91 | firstPart = input.substr(0, index).trim() 92 | secondPart = input.substr(index + 1).trim() 93 | return { 94 | type: 'pair' 95 | key: firstPart 96 | value: secondPart 97 | } 98 | 99 | deepParsePairString = (input) -> 100 | node = shallowParsePairString(input) 101 | node.value = parse(node.value) 102 | return node 103 | 104 | deepParseObjectString = (input) -> 105 | node = shallowParseObjectString(input) 106 | for value, index in node.value 107 | node.value[index] = parse(value) 108 | return node 109 | 110 | parse = (input) -> 111 | type = determineType(input) 112 | if type is 'object' 113 | return deepParseObjectString(input) 114 | if type is 'string' 115 | return stringToNode(input) 116 | if type is 'pair' 117 | return deepParsePairString(input) 118 | if type is 'array' 119 | return deepParseArrayString(input) 120 | 121 | module.exports = parse 122 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --compilers coffee:coffee-script/register -------------------------------------------------------------------------------- /test/test_checker.coffee: -------------------------------------------------------------------------------- 1 | 2 | checker = require '../src/checker' 3 | 4 | {expect} = require 'chai' 5 | 6 | 7 | describe 'checker', () -> 8 | it 'should work on nested objects and arrays', () -> 9 | val = { 10 | key: { 11 | inner: [ 12 | { 13 | innerInnerKey: '' 14 | anotherKey: 42 15 | } 16 | ] 17 | } 18 | } 19 | output = checker('{ key: {inner: [ {innerInnerKey: string, anotherKey} ]} }', val) 20 | 21 | expect(output.isValid).to.equal(true) 22 | 23 | it 'should show stack when there is a missing key (halts at the first error)', () -> 24 | val = { 25 | key: { 26 | inner: [ 27 | { 28 | keyThatShouldFail: '' 29 | } 30 | ] 31 | } 32 | } 33 | output = checker('{ key: {inner: [ {innerInnerKey: string} ]} }', val) 34 | 35 | expect(output.isValid).to.equal(false) 36 | expect(output.stack).to.deep.equal(['key', 'inner', 0, 'innerInnerKey']) 37 | 38 | it 'should show stack when there is an incorrect type (halts at the first error)', () -> 39 | val = { 40 | key: { 41 | inner: [ 42 | { 43 | innerInnerKey: 'good key' 44 | } 45 | { 46 | innerInnerKey: 42 47 | } 48 | 49 | ] 50 | } 51 | } 52 | output = checker('{ key: {inner: [ {innerInnerKey: string} ]} }', val) 53 | 54 | expect(output.isValid).to.equal(false) 55 | expect(output.stack).to.deep.equal(['key', 'inner', 1, 'innerInnerKey']) 56 | 57 | it 'should work with an array of types', () -> 58 | output = checker('[ string ]', ['element']) 59 | expect(output.isValid).to.equal(true) 60 | 61 | output = checker('[ string ]', ['element', 42]) 62 | expect(output.isValid).to.equal(false) 63 | expect(output.stack).to.deep.equal([1]) 64 | 65 | it 'should return a function if only the query is given (currying)', () -> 66 | query = checker('{key}') 67 | expect(query instanceof Function).to.equal(true) 68 | expect(query({key: 'value'}).isValid).to.equal(true) 69 | -------------------------------------------------------------------------------- /test/test_parser.coffee: -------------------------------------------------------------------------------- 1 | 2 | {expect} = require 'chai' 3 | 4 | parser = require '../src/parser' 5 | 6 | 7 | describe 'Parser', () -> 8 | 9 | it 'should parse { key1: { nestedKey } , key2 }', () -> 10 | val = parser('{ key1: { nestedKey }, key2 }') 11 | expect(val).to.deep.equal { 12 | type: 'object' 13 | value: [ 14 | { 15 | type: 'pair' 16 | key: 'key1' 17 | value: { 18 | type: 'object' 19 | value: [ 20 | { 21 | type: 'string' 22 | value: 'nestedKey' 23 | } 24 | ] 25 | } 26 | }, 27 | { 28 | type: 'string' 29 | value: 'key2' 30 | } 31 | ] 32 | } 33 | 34 | 35 | 36 | it 'should parse { key1: [ ], key2 }', () -> 37 | val = parser('{ key1: [ ], key2 }') 38 | expect(val).to.deep.equal { 39 | type: 'object' 40 | value: [ 41 | { 42 | type: 'pair' 43 | key: 'key1' 44 | value: { 45 | type: 'array' 46 | value: [] 47 | } 48 | }, 49 | { 50 | type: 'string' 51 | value: 'key2' 52 | } 53 | ] 54 | } 55 | 56 | it 'should parse [ { key1 }, { key2 } ]', () -> 57 | val = parser('[ { key1 }, { key2 } ]') 58 | expect(val).to.deep.equal { 59 | type: 'array' 60 | value: [ 61 | { 62 | type: 'object' 63 | value: [ 64 | { 65 | type: 'string' 66 | value: 'key1' 67 | } 68 | ] 69 | }, 70 | { 71 | type: 'object' 72 | value: [ 73 | { 74 | type: 'string' 75 | value: 'key2' 76 | } 77 | ] 78 | } 79 | ] 80 | } 81 | it 'should parse [ { key1, nuther }, { key2 } ]', () -> 82 | val = parser('[ { key1, nuther }, { key2 } ]') 83 | expect(val).to.deep.equal { 84 | type: 'array' 85 | value: [ 86 | { 87 | type: 'object' 88 | value: [ 89 | { 90 | type: 'string' 91 | value: 'key1' 92 | } 93 | { 94 | type: 'string' 95 | value: 'nuther' 96 | } 97 | ] 98 | }, 99 | { 100 | type: 'object' 101 | value: [ 102 | { 103 | type: 'string' 104 | value: 'key2' 105 | } 106 | ] 107 | } 108 | ] 109 | } 110 | 111 | --------------------------------------------------------------------------------