Last active
July 21, 2022 19:00
-
-
Save lukestanley/a637907e10ceddee6ef117b21eba6f39 to your computer and use it in GitHub Desktop.
WIP, Somewhat generic functions to converts a Kendraio App JSON "website template" to a JSON Schema that can be filled in
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// We already have a system that allows creating templates with empty placeholder sections for pages of sites as JSON. | |
// We need to take JSON that looks like this and turn it into editable real JSON Schema: | |
template = { | |
"name": "Great Example", | |
"domain": "great.example", | |
"websitemodel": [ | |
{ | |
"uuid": "44c2f597-79ef-49ed-9769-681f72f6b15e", | |
"name": "About and 404 pages", | |
"pagemodel": [ | |
{ | |
"name": "About", | |
"uuid": "e3f2f6ec-c426-4bd9-8494-69ed66d06a83", | |
"blockmodel": [ | |
{ | |
"name": "Image", | |
"uuid": "c5bfac02-f0b6-4c31-8fdf-18de02667ee9" | |
}, | |
{ | |
"name": "Text", | |
"uuid": "662bee68-dc07-4306-9fc5-59a54df71576" | |
}, | |
{ | |
"name": "Video", | |
"uuid": "c35dc4d1-aad2-4506-a003-2a1ecd445183" | |
} | |
], | |
}, | |
{ | |
"name": "404", | |
"uuid": "b352965b-58d0-4a20-9258-cc3b30a0e1c1", | |
"blockmodel": [ | |
{ | |
"name": "Image", | |
"uuid": "c5bfac02-f0b6-4c31-8fdf-18de02667ee9" | |
} | |
] | |
} | |
] | |
} | |
] | |
} | |
function clone(originalObject){ | |
const stringy = JSON.stringify(originalObject); | |
console.log(stringy); | |
return JSON.parse(stringy); | |
} | |
function uuidv4() { | |
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { | |
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); | |
return v.toString(16); | |
}); | |
} | |
blockTypeDefaults = { | |
"c5bfac02-f0b6-4c31-8fdf-18de02667ee9": { | |
"type":"object", | |
"properties": { | |
"name": { | |
"type": "string", | |
"default": "Image", | |
"readOnly": true, | |
"title": "Image" | |
}, | |
"src": { | |
"type": "string", | |
"title": "Image URL" | |
} | |
} | |
}, | |
"662bee68-dc07-4306-9fc5-59a54df71576": { | |
"type":"object", | |
"properties": { | |
"name": { | |
"type": "string", | |
"default": "Text", | |
"readOnly": true, | |
"title": "Text" | |
}, | |
"content": { | |
"type": "string", | |
"title": "Text content" | |
} | |
} | |
}, | |
"c35dc4d1-aad2-4506-a003-2a1ecd445183": { | |
"type":"object", | |
"properties": { | |
"name": { | |
"type": "string", | |
"default": "Video", | |
"readOnly": true, | |
"title": "Video" | |
}, | |
"src": { | |
"type": "string", | |
"title": "Video URL" | |
} | |
} | |
} | |
}; | |
// We convert these defined object keys: | |
convertables = { | |
"websitemodel": "website-content", | |
"pagemodel": "page-content", | |
"blockmodel": "block-content" | |
}; | |
function convertKeys(object){ | |
var newObject = clone(object); | |
for (var key in object){ | |
if (convertables[key]){ | |
newObject[convertables[key]] = convertKeys(object[key]); | |
delete newObject[key]; | |
} else { | |
if (typeof object[key] === "object"){ // Recurse into objects, but not arrays. Arrays are handled below. This is a little hacky, but it works for now. We could also check the type of the value at each level and handle it accordingly. | |
newObject[key] = convertKeys(object[key]); // Recurse into objects, but not arrays. Arrays are handled below. | |
} | |
// now we handle arrays: | |
if (Array.isArray(object[key])){ | |
newObject[key] = []; // Create a new array to hold the converted objects. | |
for (var i = 0; i < object[key].length; i++){ // Loop through the original array of objects. | |
var convertedObject = convertKeys(object[key][i]); // Recursively convert each object in the array, and store it in a variable. | |
newObject[key].push(convertedObject); // Push the converted object into the new array. | |
} | |
} | |
} | |
} | |
return newObject; // Return a copy of the original object with all keys converted. | |
} | |
function applyBlockTypeDefaults(object){ | |
// We need to recursively loop through the object and apply blockTypeDefaults to each block-content key. | |
// This means that when we find an object with a UUID matching an expected key, we replace it's contents entirely with a copy of the defaults. | |
var newObject = clone(object); // Make a copy of the original object. | |
// first we get the UUID keys of the blockTypeDefaults, then we recurse for matches, changing parents where found. | |
var blockTypeDefaultKeys = Object.keys(blockTypeDefaults); // Get the keys of the blockTypeDefaults object. These are UUIDs, and we will use them to match against our template's UUIDs. | |
for (var key in object){ // Loop through each key in the original object: | |
if (typeof object[key] === "object"){ // If it is an array or an object, recurse into it: | |
newObject[key] = applyBlockTypeDefaults(object[key]); // Recursively call this function on any objects found within this one, replacing them with their converted versions as they are returned from recursion. | |
if (Array.isArray(newObject[key]) && newObject["block-content"]){// If we have a block-content array that contains objects with uuids matching those of our defaults... This is a little hacky because I'm not sure how to handle arrays at each level without checking for specific keys like this... but it works for now! :) | |
var tempArray = []; | |
for (var i = 0; i < newObject[key].length; i++){ // Loop through the array of objects. | |
if (blockTypeDefaultKeys.indexOf(newObject[key][i]["uuid"]) > -1){ // If we find a UUID that matches one of our defaults... | |
tempArray.push(clone(blockTypeDefaults[newObject[key][i]["uuid"]])); // Push a copy of the default object into our temporary array, replacing it's contents entirely with those from blockTypeDefaults. | |
tempArray[tempArray.length - 1]["uuid"] = uuidv4(); // Add a new UUID v4 property to each item. | |
} else { | |
tempArray.push(newObject[key][i]);// Otherwise just push the original object into our temporary array as is, without modification or replacement by defaults: | |
} | |
} | |
newObject["block-content"] = tempArray;// Replace this key's value with an entirely new version containing only converted objects and no originals: | |
} | |
} | |
}; | |
return newObject; // Return a copy of the original object with all keys converted and replaced by their default values where applicable! | |
} | |
function arrayOfUUIDsToDict(object){ | |
function arrayChildrenAllHaveUUIDs(array){ | |
for (var i = 0; i < array.length; i++){ | |
if (!array[i]["uuid"]) return false; // If any child object does not have a UUID, we return false and do nothing to this array. | |
} | |
return true; // Otherwise we can assume that all children of the array have UUIDs, so it is safe to convert them into an object with those keys! | |
} | |
var newObject = clone(object); // Make a copy of the original object. | |
for (var key in newObject){// Loop through each key in the original object: | |
if (typeof newObject[key] === "object"){ // If it is an array or an object, recurse into it: | |
newObject[key] = arrayOfUUIDsToDict(newObject[key]); // Recursively call this function on any objects found within this one, replacing them with their converted versions as they are returned from recursion. | |
if (Array.isArray(newObject[key]) && arrayChildrenAllHaveUUIDs(newObject[key])){ | |
var tempObj = {}; | |
for (var i = 0; i < newObject[key].length; i++){ // Loop through the original child objects in the original parent object's key value: | |
tempObj[newObject[key][i]["uuid"]] = newObject[key][i]; // Add each child object to a temporary object, using the UUID as the key. | |
delete tempObj[newObject[key][i]["uuid"]].uuid; // Delete the UUID from each child object once it is used as a key instead. | |
} | |
newObject[key] = tempObj; // Replace the original parent object's key value with our temporary object containing all of the child objects, now using their UUIDs as keys. | |
} | |
} | |
}; | |
return newObject; // Return a copy of the original object with all arrays converted to objects where applicable! | |
} | |
function convertObjectsToSchemasAsNeeded(object){ | |
var newObject = clone(object); // Make a copy of the original object. | |
for (var key in newObject){// Loop through each key in the original object: | |
if (typeof newObject[key] === "object"){ // If it is an array or an object, recurse into it: | |
newObject[key] = convertObjectsToSchemasAsNeeded(newObject[key]); // Recursively call this function on any objects found within this one, replacing them with their converted versions as they are returned from recursion. | |
if (!newObject[key]["type"] && !newObject[key]["properties"]){ // If this object is plain JSON (missing a type or properties keys) we convert it to a schema by adding a "type" property to each object, with a value of "object" with the original object as a default readOnly property, and instead of the "name" property of the original object, we need to add a title property. Even the top level object need correcting. | |
newObject[key] = { | |
"type": "object", | |
"properties": { | |
"default": { | |
"type": "object", | |
"default": newObject[key], | |
"readOnly": true | |
}, | |
"title": { | |
"type": "string", | |
"default": newObject[key]["name"], | |
"readOnly": true | |
} | |
} | |
} | |
} | |
} | |
}; | |
return newObject; // Return a copy of the original object with all arrays converted to objects where applicable | |
} | |
function convertToValidJSONSchema(template) { | |
let schema = { | |
"type": "object", | |
"properties": {} | |
} | |
for (let key in template) { | |
if (typeof template[key] === "object") { | |
// Only modify invalid JSON schema fragments | |
if (template[key].type === undefined) { | |
schema.properties[key] = convertToValidJSONSchema(template[key]) | |
} else { | |
schema.properties[key] = template[key] | |
} | |
} else { | |
schema.properties[key] = { | |
"type": "string", | |
"default": template[key], | |
"readOnly": true, | |
"title": template[key] | |
} | |
} | |
} | |
return schema | |
} | |
function convertTemplateToSchema(template){ | |
return convertToValidJSONSchema(arrayOfUUIDsToDict(applyBlockTypeDefaults(convertKeys(template)))) | |
} | |
JSON.stringify(convertTemplateToSchema(template)) | |
// Outputs something like: | |
{ | |
"type": "object", | |
"properties": { | |
"name": { | |
"type": "string", | |
"default": "Great Example", | |
"readOnly": true, | |
"title": "Great Example" | |
}, | |
"domain": { | |
"type": "string", | |
"default": "great.example", | |
"readOnly": true, | |
"title": "great.example" | |
}, | |
"website-content": { | |
"type": "object", | |
"properties": { | |
"44c2f597-79ef-49ed-9769-681f72f6b15e": { | |
"type": "object", | |
"properties": { | |
"name": { | |
"type": "string", | |
"default": "About and 404 pages", | |
"readOnly": true, | |
"title": "About and 404 pages" | |
}, | |
"page-content": { | |
"type": "object", | |
"properties": { | |
"e3f2f6ec-c426-4bd9-8494-69ed66d06a83": { | |
"type": "object", | |
"properties": { | |
"name": { | |
"type": "string", | |
"default": "About", | |
"readOnly": true, | |
"title": "About" | |
}, | |
"block-content": { | |
"type": "object", | |
"properties": { | |
"4e4a2398-ea2e-4a9f-bf80-af0525c483b1": { | |
"type": "object", | |
"properties": { | |
"name": { | |
"type": "string", | |
"default": "Image", | |
"readOnly": true, | |
"title": "Image" | |
}, | |
"src": { | |
"type": "string", | |
"title": "Image URL" | |
} | |
} | |
}, | |
"acb34f3d-be2a-4357-9405-a758693bb8d5": { | |
"type": "object", | |
"properties": { | |
"name": { | |
"type": "string", | |
"default": "Text", | |
"readOnly": true, | |
"title": "Text" | |
}, | |
"content": { | |
"type": "string", | |
"title": "Text content" | |
} | |
} | |
}, | |
"cb9239bf-a60f-40d9-b0c3-422012c07e4f": { | |
"type": "object", | |
"properties": { | |
"name": { | |
"type": "string", | |
"default": "Video", | |
"readOnly": true, | |
"title": "Video" | |
}, | |
"src": { | |
"type": "string", | |
"title": "Video URL" | |
} | |
} | |
} | |
} | |
} | |
} | |
}, | |
"b352965b-58d0-4a20-9258-cc3b30a0e1c1": { | |
"type": "object", | |
"properties": { | |
"name": { | |
"type": "string", | |
"default": "404", | |
"readOnly": true, | |
"title": "404" | |
}, | |
"block-content": { | |
"type": "object", | |
"properties": { | |
"25b9bc09-93d6-498e-b14c-1e74e0697aae": { | |
"type": "object", | |
"properties": { | |
"name": { | |
"type": "string", | |
"default": "Image", | |
"readOnly": true, | |
"title": "Image" | |
}, | |
"src": { | |
"type": "string", | |
"title": "Image URL" | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment