Skip to content

Instantly share code, notes, and snippets.

@pmuellr
Created January 16, 2015 05:01
Show Gist options
  • Save pmuellr/357cb018e001379c9d7f to your computer and use it in GitHub Desktop.
Save pmuellr/357cb018e001379c9d7f to your computer and use it in GitHub Desktop.
sloppy json template strings for es6 - runs in io.js
// es6 template strings for handling sloppy json
// see: https://github.com/pmuellr/sloppy_json_parse
// sloppy json is json, with the following differences:
// - commas are optional
// - comments start with the '#' character and end with the end of the line
// - strings which match the regex /[$_A-Z][$\w]*/i don't need to be quoted,
// except that the strings "null", "true", and "false" must always be quoted.
"use strict"
function main() {
let x = sloppyJSON`{
a: 1
b: 2
}`
test(x)
// prints: {"a":1,"b":2}
test(sloppyJSON`{a: 1 b: 2}`)
// prints: {"a":1,"b":2}
test(sloppyJSON`{
a: ${x.b}
b: ${x.a}
}`)
// prints: {"a":2,"b":1}
test(sloppyJSON`[1 2 3 dogs cats]`)
// prints: [1,2,3,"dogs","cats"]
test(sloppyJSON`{
comments: bob # yay!
are: jim # more!
useful: earl # don't stop!
}`)
// prints: {"comments":"bob","are":"jim","useful":"earl"}
}
//------------------------------------------------------------------------------
function test(string) {
console.log(JSON.stringify(string))
}
//------------------------------------------------------------------------------
function sloppyJSON(lits) {
let subs = [].slice.call(arguments, 1)
let source = []
for (let i=0; i<subs.length; i++) {
source.push(lits[i])
source.push(subs[i])
}
source.push(lits[lits.length-1])
source = source.join("")
let result
let ePrefix = `error compiling '${JSON.stringify(source)}'`
try {
result = sloppy_json_parse(source)
}
catch (e) {
throw new Error(`${ePrefix}: error compiling sloppy json: ${e.message}`)
}
return result
}
// everything below from: https://github.com/pmuellr/sloppy_json_parse/sloppy_json_parse-test.js
//----------------------------------------------------------------------------
// based on: http://www.json.org/json_parse.js
//----------------------------------------------------------------------------
// This parser parses SloppyJSON instead of JSON. SloppyJSON is different
// from JSON in the following respects:
// - commas are never required
// - comments are allowed; they start with '#' and end at the end of the line
// - strings which match /[$_A-Z][$\w]*/i need not be quoted, except for
// the strings "null", "true", and "false"
//----------------------------------------------------------------------------
// Public Domain
//----------------------------------------------------------------------------
// 2010-08-03 - initial conversion of json_parse.js to sloppy_json_parse.js
//----------------------------------------------------------------------------
/*
http://www.JSON.org/json_parse.js
2009-05-31
Public Domain.
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
This file creates a json_parse function.
json_parse(text, reviver)
This method parses a JSON text to produce an object or array.
It can throw a SyntaxError exception.
The optional reviver parameter is a function that can filter and
transform the results. It receives each of the keys and values,
and its return value is used instead of the original value.
If it returns what it received, then the structure is not modified.
If it returns undefined then the member is deleted.
Example:
// Parse the text. Values that look like ISO date strings will
// be converted to Date objects.
myData = json_parse(text, function (key, value) {
var a;
if (typeof value === 'string') {
a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
if (a) {
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+a[5], +a[6]));
}
}
return value;
});
This is a reference implementation. You are free to copy, modify, or
redistribute.
This code should be minified before deployment.
See http://javascript.crockford.com/jsmin.html
USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
NOT CONTROL.
*/
/*members "", "\"", "\/", "\\", at, b, call, charAt, f, fromCharCode,
hasOwnProperty, message, n, name, push, r, t, text
*/
var sloppy_json_parse = (function () {
// This is a function that can parse a JSON text, producing a JavaScript
// data structure. It is a simple, recursive descent parser. It does not use
// eval or regular expressions, so it can be used as a model for implementing
// a JSON parser in other languages.
// We are defining the function inside of another function to avoid creating
// global variables.
var at, // The index of the current character
ch, // The current character
identBegin = /[$_A-Z]/i,
identRest = /[$\w]/,
escapee = {
'"': '"',
'\\': '\\',
'/': '/',
b: '\b',
f: '\f',
n: '\n',
r: '\r',
t: '\t'
},
text,
error = function (m) {
// Call error when something is wrong.
throw {
name: 'SyntaxError',
message: m,
at: at,
text: text
};
},
next = function (c) {
// If a c parameter is provided, verify that it matches the current character.
if (c == ',' && ch != ',') return ch;
if (c && c !== ch) {
error("Expected '" + c + "' instead of '" + ch + "'");
}
// Get the next character. When there are no more characters,
// return the empty string.
ch = text.charAt(at);
at += 1;
return ch;
},
number = function () {
// Parse a number value.
var number,
string = '';
if (ch === '-') {
string = '-';
next('-');
}
while (ch >= '0' && ch <= '9') {
string += ch;
next();
}
if (ch === '.') {
string += '.';
while (next() && ch >= '0' && ch <= '9') {
string += ch;
}
}
if (ch === 'e' || ch === 'E') {
string += ch;
next();
if (ch === '-' || ch === '+') {
string += ch;
next();
}
while (ch >= '0' && ch <= '9') {
string += ch;
next();
}
}
number = +string;
if (isNaN(number)) {
error("Bad number");
} else {
return number;
}
},
string = function () {
// Parse a string value.
var hex,
i,
string = '',
uffff;
// When parsing for string values, we must look for " and \ characters.
if (ch === '"') {
while (next()) {
if (ch === '"') {
next();
return string;
} else if (ch === '\\') {
next();
if (ch === 'u') {
uffff = 0;
for (i = 0; i < 4; i += 1) {
hex = parseInt(next(), 16);
if (!isFinite(hex)) {
break;
}
uffff = uffff * 16 + hex;
}
string += String.fromCharCode(uffff);
} else if (typeof escapee[ch] === 'string') {
string += escapee[ch];
} else {
break;
}
} else {
string += ch;
}
}
}
string = word()
if (typeof string == "string") return string
error("expected string and not '" + string + "'");
},
comment = function () {
while (ch && ch != '\n') {
next();
}
},
white = function () {
// Skip whitespace.
while (ch && (ch <= ' ' || ch == '#')) {
if (ch == '#') comment();
next();
}
},
word = function () {
// true, false, or null.
var result = "";
if (!identBegin.test(ch)) error("Unexpected '" + ch + "'");
while (identRest.test(ch)) {
result += ch;
next();
}
switch(result) {
case 'true': return true;
case 'false': return false;
case 'null': return null;
default: return result;
}
},
value, // Place holder for the value function.
array = function () {
// Parse an array value.
var array = [];
if (ch === '[') {
next('[');
white();
if (ch === ']') {
next(']');
return array; // empty array
}
while (ch) {
array.push(value());
white();
if (ch === ']') {
next(']');
return array;
}
next(',');
white();
}
}
error("Bad array");
},
object = function () {
// Parse an object value.
var key,
object = {};
if (ch === '{') {
next('{');
white();
if (ch === '}') {
next('}');
return object; // empty object
}
while (ch) {
key = string();
white();
next(':');
if (Object.hasOwnProperty.call(object, key)) {
error('Duplicate key "' + key + '"');
}
object[key] = value();
white();
if (ch === '}') {
next('}');
return object;
}
next(',');
white();
}
}
error("Bad object");
};
value = function () {
// Parse a JSON value. It could be an object, an array, a string, a number,
// or a word.
white();
switch (ch) {
case '{':
return object();
case '[':
return array();
case '"':
return string();
case '-':
return number();
default:
return ch >= '0' && ch <= '9' ? number() : word();
}
};
// Return the json_parse function. It will have access to all of the above
// functions and variables.
return function (source, reviver) {
var result;
text = source;
at = 0;
ch = ' ';
result = value();
white();
if (ch) {
error("Syntax error");
}
// If there is a reviver function, we recursively walk the new structure,
// passing each name/value pair to the reviver function for possible
// transformation, starting with a temporary root object that holds the result
// in an empty key. If there is not a reviver function, we simply return the
// result.
return typeof reviver === 'function' ? (function walk(holder, key) {
var k, v, value = holder[key];
if (value && typeof value === 'object') {
for (k in value) {
if (Object.hasOwnProperty.call(value, k)) {
v = walk(value, k);
if (v !== undefined) {
value[k] = v;
} else {
delete value[k];
}
}
}
}
return reviver.call(holder, key, value);
}({'': result}, '')) : result;
};
}());
//------------------------------------------------------------------------------
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment