Skip to content

Instantly share code, notes, and snippets.

@RedRoserade
Last active October 16, 2016 22:29
Show Gist options
  • Save RedRoserade/1b76a4c4d81e3c0df51af9fcc56a357c to your computer and use it in GitHub Desktop.
Save RedRoserade/1b76a4c4d81e3c0df51af9fcc56a357c to your computer and use it in GitHub Desktop.
JSON to SwiftyJSON-enabled classes
'use strict';
const indentStr = ' ';
const swiftTypeToSwiftyJsonProp = {
'String': 'string',
'Boolean': 'boolean',
'Double': 'double'
}
function indent(line, level) {
if (typeof level === 'undefined') {
level = 1;
}
return Array.from({ length: level }).reduce((acc, l) => acc + indentStr, '') + line;
}
function indentLines(text, level) {
if (typeof level === 'undefined') {
level = 1;
}
return text.split('\n')
.map(l => indent(l, level))
.join('\n');
}
function typeOf(v, options) {
switch (typeof v) {
case "string":
if (/^\d{4}-\d{2}-\d{2}$/.test(v)) {
return { type: "Date", childTypes: options.childTypes, format: 'yyyy-MM-dd' };
}
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/.test(v)) {
return { type: "Date", childTypes: options.childTypes, format: "yyyy-MM-dd'T'HH:mm:ss" };
}
return { type: "String", childTypes: options.childTypes };
case "boolean":
return { type: "Bool", childTypes: options.childTypes };
case "number":
return { type: "Double", childTypes: options.childTypes };
}
if (Array.isArray(v)) {
return parseArray(v, options);
}
if (v === null) {
return { type: 'Any?' };
}
return parseObject(v, options);
}
function parseArray(arr, options) {
let firstItem = arr.filter(i => i !== null)[0];
let result = typeOf(firstItem, options);
return { type: `[${result.type}]`, childTypes: result.childTypes, parentType: 'Array' };
}
function parseObject(obj, options) {
let name = options.propName[0].toUpperCase() + options.propName.substr(1);
if (name.toLowerCase().indexOf("collection") !== -1 ||
options.parentType === 'Array') {
name = name.replace(/collection$/i, 'Item');
}
// Check if we have a new type.
if (Array.isArray(options.childTypes) && !options.childTypes.find(t => t.name === name)) {
let newTypeProps = Object.keys(obj)
.map(k => {
return {
name: k,
type: typeOf(obj[k], { propName: k, childTypes: options.childTypes })
};
});
options.childTypes.push({ name: name, props: newTypeProps });
return {
type: name,
childTypes: options.childTypes
};
}
return { type: name, childTypes: options.childTypes };
}
function makeInit(spec) {
let lines = [
'init('
];
let constructorArgs = spec.props.map(p => `${p.name}: ${p.type.type}`).join(', ');
lines[0] += constructorArgs;
lines[0] += ') {';
spec.props
.map(p => indent(`self.${p.name} = ${p.name}`))
.forEach(c => lines.push(c));
lines.push('}');
lines.push('');
lines.push('// MARK: - Init JSON');
lines.push('');
lines.push('convenience init(fromJson json: JSON) {')
lines.push(indent('self.init('));
let jsonInitArgs = spec.props
.map(p => {
let arg = `${p.name}: ` //json[${p.name}].???`
let typeName = p.type.type;
if (typeName === 'Any?') {
// Unknown...
arg += `json["${p.name}"] /* #warning: Unknown type */`;
} else if (typeName[0] === '[') {
// Array
let arrayTypeArg = typeName.replace(/\[|\]/g, '');
if (arrayTypeArg === 'Any') {
arg += `json["${p.name}"].array! /* #warning: Unknown type */`;
} else {
arg += `json["${p.name}"].array!.map(`
let jsonProp = swiftTypeToSwiftyJsonProp[arrayTypeArg];
if (jsonProp !== undefined) {
// Primitive type
arg += `{$0.${jsonProp}!})`;
} else {
// Complex type
arg += `${arrayTypeArg}.init)`;
}
}
} else if (typeName == 'Date') {
// Initialize with a closure
let closureSrc = `{let df = DateFormatter(); df.dateFormat = "${p.type.format}"; return df.date(from: json["${p.name}"].string!)}()`
arg += closureSrc
} else {
// Others (object, primitive)
let jsonProp = swiftTypeToSwiftyJsonProp[typeName];
if (jsonProp !== undefined) {
// Primitive type
arg += `json["${p.name}"].${jsonProp}!`;
} else {
// Complex type
arg += `${typeName}(fromJson: json["${p.name}"])`;
}
}
return arg;
})
.join(',\n');
lines.push(indentLines(jsonInitArgs, 2));
lines.push(indent(')'));
lines.push('}');
return lines.join('\n');
}
function classToString(spec) {
let lines = [
`class ${spec.name} {`
];
lines.push('');
lines.push(indent('// MARK: - Init'));
lines.push('');
// init
let initCode = makeInit(spec);
lines.push(indentLines(initCode));
lines.push('');
lines.push(indent('// MARK: - Properties'));
lines.push('');
// Props
spec.props
.map(p => {
let line = indent(`var ${p.name}: ${p.type.type}`);
return line;
})
.forEach(l => lines.push(l));
lines.push('}');
return lines.join('\n');
}
function jsonToClasses(jsonStr, rootClassName) {
let obj = JSON.parse(jsonStr);
let spec = typeOf(obj, { propName: rootClassName, childTypes: [] });
let classes = spec.childTypes.map(classToString)
classes.unshift('import Foundation')
classes.unshift('import SwiftyJSON')
let results = classes.join('\n\n');
return results;
}
// For use in a terminal:
// node json-to-swiftyjson.js <path to json> <optional root class name, defaults to "Root">
let path = process.argv[2];
let rootClassName = process.argv[3] || "Root";
if (path === undefined) {
console.log("No source path specified.");
process.exit(1);
}
let fs = require('fs');
let contents = fs.readFileSync(path, 'utf8');
let classes = jsonToClasses(contents, rootClassName);
console.log(classes);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment