alm
Version:
The best IDE for TypeScript
535 lines (453 loc) • 18.3 kB
text/typescript
/**
* json2tsd.ts
* Created by xperiments on 19/04/14.
* From https://github.com/xperiments/json2dts/blob/master/src/json2dts.ts
* BAS: 1 minor modification
*/
namespace is {
// Local references to global functions (better minification)
var ObjProto = Object.prototype;
var ArrayProto = Array.prototype;
var toString = ObjProto.toString;
var hasOwn = ObjProto.hasOwnProperty;
// in case the browser doesn't have it
var index_of = (
ArrayProto.indexOf ?
function(arr, val) { return arr.indexOf(val); } :
function(arr, val) {
for (var i = 0, l = arr.length; i < l; i++) {
if (arr[i] === val) {
return i;
}
}
return -1;
}
)
// Primitives - test for type exactness
// typeof is faster; see http://jsperf.com/underscore-js-istype-alternatives/7
// typeof does not catch values made with a constructor, but is still faster than to String
// see : http://jsperf.com/is-js
export function string(s): boolean {
return (typeof s === 'string') || s instanceof String;
}
export function number(n) { return (typeof n === 'number') || n instanceof Number; }
export function boolean(b) { return b === !!b || b instanceof Boolean; }
// non-primitive builtin types
export function fn(f) { return (typeof f === 'function'); };
// array - delegates to builtin if available
export var array = Array.isArray || function(a) { return toString.call(a) === '[object Array]'; };
// basically all Javascript types are objects
export function object(o) { return o === Object(o); };
// duck typing, because there isn't really a good way to do this
export function regex(r) { return !!(r && r.test && r.exec && (r.ignoreCase || r.ignoreCase === false)); };
// HTML elements
export var element: (e) => boolean = (
typeof HTMLElement !== 'undefined' ?
function(e) { return (e instanceof HTMLElement); } :
// https://github.com/documentcloud/underscore/blob/master/underscore.js#L836-838
function(e) { return !!(e && e.nodeType === 1); }
);
// non-strict type checking
// http://dl.dropbox.com/u/35146/js/tests/isNumber.html
export function numeric(n) { return !isNaN(parseFloat(n)) && isFinite(n); };
// plain objects - not a specific type, just an object with key/value pairs
// https://github.com/jquery/jquery/blob/c14a6b385fa419ce67f115e853fb4a89d8bd8fad/src/core.js#L425-452
export function hash(o) {
// fail fast for falsy/non-object/HTMLElement/window objects
// also check constructor properties - objects don't have their own constructor,
// and their constructor does not have its own `isPrototypeOf` function
if (!o || typeof o !== 'object' || is.element(o) || (typeof window !== 'undefined' && o === window) ||
(o.constructor && !hasOwn.call(o, 'constructor') && !hasOwn.call(o.constructor.prototype, 'isPrototypeOf'))
) {
return false;
}
// from jQuery source: speed up the test by cycling to the last property,
// since own properties are iterated over first and therefore the last one will
// indicate the presence of any prototype properties
for (var key in o) { }
return (key === undefined || hasOwn.call(o, key));
};
// test for containment, in both arrays and objects
export function inside(container, val) {
if (is.array(container)) {
return index_of(container, val) > -1;
} else if (is.object(container)) {
for (var prop in container) {
if (hasOwn.call(container, prop) && container[prop] === val) {
return true;
}
}
return false;
} else {
return false;
}
};
// test for variable being undefined or null
export function set(v) { return v !== null && v !== (void 0); };
// test for having any elements (if an array), any properties (if an object), or falsy-ness
export function empty(container) {
if (is.array(container)) {
return container.length === 0;
} else if (is.object(container)) {
// when an object has a valueOf function that doesn't return an object,
// object is empty if value is empty
if (is.fn(container.valueOf) && !is.object(container.valueOf())) {
return is.empty(container.valueOf());
}
for (var x in container) {
if (hasOwn.call(container, x)) {
return false;
}
}
return true;
} else {
return !container;
}
}
}
/**
*
* Secure Hash Algorithm (SHA1)
* http://www.webtoolkit.info/
*
**/
function SHA1(msg: string): string {
function rotate_left(n, s) {
var t4 = (n << s) | (n >>> (32 - s));
return t4;
};
function lsb_hex(val) {
var str = "";
var i;
var vh;
var vl;
for (i = 0; i <= 6; i += 2) {
vh = (val >>> (i * 4 + 4)) & 0x0f;
vl = (val >>> (i * 4)) & 0x0f;
str += vh.toString(16) + vl.toString(16);
}
return str;
};
function cvt_hex(val) {
var str = "";
var i;
var v;
for (i = 7; i >= 0; i--) {
v = (val >>> (i * 4)) & 0x0f;
str += v.toString(16);
}
return str;
};
function Utf8Encode(string) {
string = string.replace(/\r\n/g, "\n");
var utftext = "";
for (var n = 0; n < string.length; n++) {
var c = string.charCodeAt(n);
if (c < 128) {
utftext += String.fromCharCode(c);
}
else if ((c > 127) && (c < 2048)) {
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
}
else {
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
}
}
return utftext;
};
var blockstart;
var i, j;
var W = new Array(80);
var H0 = 0x67452301;
var H1 = 0xEFCDAB89;
var H2 = 0x98BADCFE;
var H3 = 0x10325476;
var H4 = 0xC3D2E1F0;
var A, B, C, D, E;
var temp;
msg = Utf8Encode(msg);
var msg_len = msg.length;
var word_array = new Array();
for (i = 0; i < msg_len - 3; i += 4) {
j = msg.charCodeAt(i) << 24 | msg.charCodeAt(i + 1) << 16 |
msg.charCodeAt(i + 2) << 8 | msg.charCodeAt(i + 3);
word_array.push(j);
}
switch (msg_len % 4) {
case 0:
i = 0x080000000;
break;
case 1:
i = msg.charCodeAt(msg_len - 1) << 24 | 0x0800000;
break;
case 2:
i = msg.charCodeAt(msg_len - 2) << 24 | msg.charCodeAt(msg_len - 1) << 16 | 0x08000;
break;
case 3:
i = msg.charCodeAt(msg_len - 3) << 24 | msg.charCodeAt(msg_len - 2) << 16 | msg.charCodeAt(msg_len - 1) << 8 | 0x80;
break;
}
word_array.push(i);
while ((word_array.length % 16) != 14) word_array.push(0);
word_array.push(msg_len >>> 29);
word_array.push((msg_len << 3) & 0x0ffffffff);
for (blockstart = 0; blockstart < word_array.length; blockstart += 16) {
for (i = 0; i < 16; i++) W[i] = word_array[blockstart + i];
for (i = 16; i <= 79; i++) W[i] = rotate_left(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16], 1);
A = H0;
B = H1;
C = H2;
D = H3;
E = H4;
for (i = 0; i <= 19; i++) {
temp = (rotate_left(A, 5) + ((B & C) | (~B & D)) + E + W[i] + 0x5A827999) & 0x0ffffffff;
E = D;
D = C;
C = rotate_left(B, 30);
B = A;
A = temp;
}
for (i = 20; i <= 39; i++) {
temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0x6ED9EBA1) & 0x0ffffffff;
E = D;
D = C;
C = rotate_left(B, 30);
B = A;
A = temp;
}
for (i = 40; i <= 59; i++) {
temp = (rotate_left(A, 5) + ((B & C) | (B & D) | (C & D)) + E + W[i] + 0x8F1BBCDC) & 0x0ffffffff;
E = D;
D = C;
C = rotate_left(B, 30);
B = A;
A = temp;
}
for (i = 60; i <= 79; i++) {
temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0xCA62C1D6) & 0x0ffffffff;
E = D;
D = C;
C = rotate_left(B, 30);
B = A;
A = temp;
}
H0 = (H0 + A) & 0x0ffffffff;
H1 = (H1 + B) & 0x0ffffffff;
H2 = (H2 + C) & 0x0ffffffff;
H3 = (H3 + D) & 0x0ffffffff;
H4 = (H4 + E) & 0x0ffffffff;
}
var temp2 = cvt_hex(H0) + cvt_hex(H1) + cvt_hex(H2) + cvt_hex(H3) + cvt_hex(H4);
return temp2.toLowerCase();
}
export class Types {
static STRING: string = 'string';
static NUMBER: string = 'number';
static BOOLEAN: string = 'boolean';
static ARRAY: string = '[]';
}
export class Json2dts {
moduleName: string;
classes: any;
classesCache: { [sha: string]: string };
classesInUse: { [name: string]: number };
constructor() { }
public parse(obj: any, objectName: string = "_RootInterface", moduleName: string = ""): any {
this.moduleName = moduleName;
this.classes = {};
this.classesCache = {};
this.classesInUse = {};
this.analyse_object(obj, objectName);
return this.classes;
}
public getCode() {
var output: string;
var classes: { [name: string]: string } = {};
var outputModule: boolean = this.moduleName == "" ? false : true;
var interfaceTab: string = outputModule ? "\t" : "";
var propertyTab: string = interfaceTab + "\t";
Object.keys(this.classes).map((clsName: string) => {
output = interfaceTab + "interface " + clsName + '\n' + interfaceTab + '{\n';
Object.keys(this.classes[clsName]).map((key: string) => {
/** BAS: added a space `:` => `: ` */
output += propertyTab + key + ': ' + this.classes[clsName][key] + ';\n';
})
output += interfaceTab + '}\n\n';
classes[clsName] = output;
})
output = outputModule ? "module " + this.moduleName + "\n{\n" : "";
Object.keys(classes).sort().forEach((key: string) => { output += classes[key] });
return output + (outputModule ? "\n}" : "");
}
public analyse_object(obj: any, objectName: string = "json"): void {
// determine object name
objectName = this.getInterfaceType(objectName, obj);
// initialize named class object
this.classes[objectName] = this.classes[objectName] || {}
// loops over all object properties and
// determines each property type
Object.keys(obj).map((key: string) => {
var type: string = "string";
var sha: string = "";
var value: any = obj[key];
switch (true) {
case is.string(value): type = Types.STRING; break;
case is.number(value): type = Types.NUMBER; break;
case is.boolean(value): type = Types.BOOLEAN; break;
case is.array(value):
// default typed array
type = "any[]";
// if value is consisten over
// all items of the array
if (this.is_value_consistent(value)) {
// is an empty array
if (this.size(value) == 0) {
type = "any[]"; // EMPTY ARRAY
}
else {
// consistent value is an object?
if (is.object(value[0])) {
type = this.getInterfaceType(key, value[0]) + '[]';
this.analyse_object(value[0], key);
}
// conststent value is a basic type
else {
type = this.getBasicType(value[0]) + '[]';
}
}
}
break;
case is.object(value) && !is.array(value):
// default object type
type = "any";
// if object is not empty
// set as current type and process it
if (!is.empty(value)) {
type = this.getInterfaceType(key, value)
this.analyse_object(value, key);
}
break;
}
// if key has any special char, quote it.
if (this.hasSpecialChars((key))) {
key = '\"' + key + '\"';
}
this.classes[objectName][key] = type;
});
}
private objectParser(key: string) {
}
private getBasicType(value: any): string {
var type: string = Types.STRING;
switch (true) {
case is.string(value): type = Types.STRING; break;
case is.number(value): type = Types.NUMBER; break;
case is.boolean(value): type = Types.BOOLEAN; break;
}
return type;
}
private is_value_consistent = function(o: any): boolean {
if (this.size(o) == 0) {
return true;
}
else {
if (!is.array(o)) {
o = this.values(o);
}
var n = o[0];
var nn = (is.object(n) ? this.generate_signature(n) : typeof n);
return Object.keys(o).every((key) => {
return (is.object(o[key]) ? this.generate_signature(o[key]) : typeof o[key]) == nn;
});
}
}
private getInterfaceType(key: string, value: any): string {
// get a valid className
key = key.replace(/_/gi, ' ').replace(/-/gi, ' ').replace(/\w\S*/g, (txt) => {
return txt.charAt(0).toUpperCase() + txt.substr(1);
}).replace(/ /gi, '');
// check if a definition exist for the current Interface signature
var currentObjectSignature: string = this.generate_signature(value);
var isKnownClass: boolean = Object.keys(this.classesCache).indexOf(currentObjectSignature) != -1;
// its a know type, return it
if (isKnownClass) return this.classesCache[currentObjectSignature];
// current type name is already used by other Interface
if (this.classesInUse[key] != undefined) {
// update key count
this.classesInUse[key]++;
// initialize Interface Name Object
this.classesCache[currentObjectSignature] = key + this.classesInUse[key];
return this.classesCache[currentObjectSignature];
}
// current Interface Name was never used
this.classesCache[currentObjectSignature] = key;
this.classesInUse[key] = 0;
return key;
}
private size(obj: any): number {
if (obj == null) return 0;
return (obj.length === +obj.length) ? obj.length : Object.keys(obj).length;
}
private values(obj: any) {
return Object.keys(obj).map((key) => { return obj[key] });
}
private generate_signature(o: any) {
if (is.object(o)) {
return SHA1(Object.keys(o).map((n) => {
return n.toLowerCase();
}).sort().join('|'));
}
else {
return SHA1(Object.keys(o).map((n) => {
return typeof n;
}).sort().join('|'));
}
}
private hasSpecialChars(str: string): boolean {
return /[ ~`!#$%\^&*+=\-\[\]\\';,\/{}|\\":<>\?]/g.test(str);
}
private keysrt(key: string, desc: boolean = false): (a: string, b: string) => number {
return (a, b) => {
return desc ? ~~(a[key] < b[key]) : ~~(a[key] > b[key]);
}
}
}
function extractLineFeeds(s: string): string {
return s.replace(/[^\n]+/g, '');
}
export function toValidJSON(input: string, keepLineNumbers: boolean = false): string {
var UNESCAPE_MAP = { '\\"': '"', "\\`": "`", "\\'": "'" };
var ML_ESCAPE_MAP = { '\n': '\\n', "\r": '\\r', "\t": '\\t', '"': '\\"' };
function unescapeQuotes(r) { return UNESCAPE_MAP[r] || r; }
return input.replace(/`(?:\\.|[^`])*`|'(?:\\.|[^'])*'|"(?:\\.|[^"])*"|\/\*[^]*?\*\/|\/\/.*\n?/g, // pass 1: remove comments
(s: string) => {
if (s.charAt(0) == '/') {
return keepLineNumbers ? extractLineFeeds(s) : '';
}
else {
return s;
}
})
.replace(/(?:true|false|null)(?=[^\w_$]|$)|([a-zA-Z_$][\w_$]*)|`((?:\\.|[^`])*)`|'((?:\\.|[^'])*)'|"(?:\\.|[^"])*"|(,)(?=\s*[}\]])/g, // pass 2: requote
(s: string, identifier: string, multilineQuote: string, singleQuote: string, lonelyComma: boolean) => {
if (lonelyComma) {
return '';
}
else if (identifier != null) {
return '"' + identifier + '"';
}
else if (multilineQuote != null) {
return '"' + multilineQuote.replace(/\\./g, unescapeQuotes).replace(/[\n\r\t"]/g, function(r) { return ML_ESCAPE_MAP[r]; }) +
'"' + (keepLineNumbers ? extractLineFeeds(multilineQuote) : '');
}
else if (singleQuote != null) {
return '"' + singleQuote.replace(/\\./g, unescapeQuotes).replace(/"/g, '\\"') + '"';
}
else {
return s;
}
});
}