Skip to content

Instantly share code, notes, and snippets.

@lukestanley
Last active July 21, 2022 19:00
Show Gist options
  • Save lukestanley/a637907e10ceddee6ef117b21eba6f39 to your computer and use it in GitHub Desktop.
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
// 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