@freckleface/golembase-js-transformations
Version:
A small library of JavaScript object transformations for golem-base
313 lines (286 loc) • 10.7 kB
JavaScript
import { Annotation } from "golem-base-sdk";
/**
* Transforms a plain JavaScript object (POJO) into an Annotations object.
* It separates properties into numeric types and converts all other types
* into string annotations.
* @param myobj The plain JavaScript object to transform.
* @returns An Annotations object.
*/
export const transformPOJOToAnnotations = (myobj) => {
const stringAnnotations = [];
const numericAnnotations = [];
for (const [key, value] of Object.entries(myobj)) {
if (typeof value === 'number' && !isNaN(value)) {
numericAnnotations.push(new Annotation(key, value));
}
else {
stringAnnotations.push(new Annotation(key, String(value)));
}
}
return {
stringAnnotations,
numericAnnotations
};
};
/**
* Transforms an Annotations object back into a single plain JavaScript object.
* @param annotations An object containing arrays of string and numeric annotations.
* @param convertBools A flag to determine if string 'true'/'false' should be converted to booleans.
* @returns A plain JavaScript object.
*/
export const transformAnnotationsToPOJO = (annotations, convertBools = true) => {
const pojo = {};
// Process all string annotations
for (const annotation of annotations.stringAnnotations) {
// Correctly convert string 'true' and 'false' to booleans
if (convertBools && (annotation.value === 'true' || annotation.value === 'false')) {
pojo[annotation.key] = annotation.value === 'true';
}
else {
pojo[annotation.key] = annotation.value;
}
}
// Process all numeric annotations
for (const annotation of annotations.numericAnnotations) {
pojo[annotation.key] = annotation.value;
}
return pojo;
};
/**
* Transforms a POJO with array values into an Annotations object.
* Each array is converted into a comma-separated string, with optional sorting.
* @param listObj The object with array values.
* @param sort A flag to determine if the arrays should be sorted before joining.
* @returns An Annotations object.
*/
export const transformListPOJOToAnnotations = (listObj, sort = true) => {
const stringAnnotations = [];
for (const [key, value] of Object.entries(listObj)) {
if (Array.isArray(value)) {
// Use a copy of the array to avoid modifying the original
let arrayToProcess = [...value];
if (sort) {
arrayToProcess.sort((a, b) => a.localeCompare(b));
}
stringAnnotations.push(new Annotation(key, arrayToProcess.join(',')));
}
}
return {
stringAnnotations,
numericAnnotations: [] // This transform assumes all lists become string annotations
};
};
/**
* Transforms an Annotations object with comma-separated strings back into a POJO with arrays.
* It dynamically detects if the array items are numeric or boolean.
* @param annotations The Annotations object.
* @param convertBools A flag to determine if string 'true'/'false' should be converted to booleans.
* @param sort A flag to determine if the resulting arrays should be sorted.
* @returns A POJO with array values.
*/
export const transformAnnotationsToListPOJO = (annotations, convertBools = true, sort = true) => {
const pojo = {};
for (const annotation of annotations.stringAnnotations) {
const key = annotation.key;
// First, create a definitive array of strings.
const stringItems = annotation.value.split(',').map(item => item.trim());
// Check if all items in the array are valid numbers.
const areAllNumeric = stringItems.length > 0 && stringItems.every(item => !isNaN(parseFloat(item)) && isFinite(Number(item)));
// Check if all items are 'true' or 'false'.
const areAllBooleans = stringItems.length > 0 && stringItems.every(item => item === 'true' || item === 'false');
if (areAllNumeric) {
// If all are numeric, parse them.
const numericValues = stringItems.map(item => parseFloat(item));
if (sort) {
numericValues.sort((a, b) => a - b);
}
pojo[key] = numericValues;
}
else if (convertBools && areAllBooleans) {
// If all are boolean strings, parse them.
const booleanValues = stringItems.map(item => item === 'true');
if (sort) {
booleanValues.sort((a, b) => Number(a) - Number(b)); // Sorts false before true
}
pojo[key] = booleanValues;
}
else {
// Otherwise, treat as a string array.
if (sort) {
stringItems.sort((a, b) => a.localeCompare(b));
}
pojo[key] = stringItems;
}
}
return pojo;
};
/**
* Prepares a JavaScript object for storage by separating it into a main data payload
* consisting of the stringified form of the JavaScript object
* and a set of searchable index annotations.
* @param sourceObject The object to transform.
* @param indexes An optional array of top-level keys from the sourceObject to create as separate annotations for indexing.
* @returns An object containing the stringified data and the generated annotations.
*/
export const transformJSONToGolemNoSQL = (sourceObject, indexes = []) => {
const stringAnnotations = [];
const numericAnnotations = [];
// 1. Stringify the entire object for the main data payload.
const dataAsString = JSON.stringify(sourceObject);
// 2. Create annotations only for the specified indexes.
for (const key of indexes) {
// Use hasOwnProperty for safety to ensure the key belongs to the object itself.
if (Object.prototype.hasOwnProperty.call(sourceObject, key)) {
const value = sourceObject[key];
if (typeof value === 'number' && !isNaN(value)) {
numericAnnotations.push(new Annotation(key, value));
}
else {
// Convert all other types to their string representation for the index.
stringAnnotations.push(new Annotation(key, String(value)));
}
}
}
return {
dataAsString,
annotations: { stringAnnotations, numericAnnotations }
};
};
/**
* Reconstructs a full JavaScript object from its stringified data representation.
* @param dataAsString The stringified JSON data payload.
* @returns The reconstructed JavaScript object.
*/
export const transformGolemNoSQLToJSON = (dataAsString) => {
try {
return JSON.parse(dataAsString);
}
catch (e) {
// Provide a more helpful error message for the library user.
throw new Error("Failed to reconstruct object: The provided data string is not valid JSON.");
}
};
/*
const listObj = {
directors: [
'Christopher Nolan',
'Denis Villeneuve',
'Gareth Edwards',
'Robert Zemeckis',
'Terry Gilliam'
],
artists: [
'Neil Young',
'Parliament',
'Pink Floyd',
'Rush',
'The Midnight',
'Vangelis',
'Yes'
],
authors: [
'Another Author',
'George R. R. Martin',
'Jeffrey Cogswell',
'Neal Stephenson',
'William Gibson'
],
movie_genres: [ 'dystopian', 'sci-fi', 'thriller' ],
music_genres: [ 'ambient', 'folk', 'funk', 'prog rock', 'synthwave' ],
book_genres: [ 'fantasy', 'cyberpunk', 'tech' ],
};
const result1 = transformListPOJOToAnnotations(listObj);
console.log(result1);
const result2 = transformAnnotationsToListPOJO(result1);
console.log(result2);
*/
/*
// --- Example Usage for List Transformers ---
// 1. Start with a POJO with arrays
const originalListObject = {
people: ['Fred Franklin', 'Julie Jackson', 'Susie Smith'],
places: ['Grand Junction', 'New York'],
favorites: [5, 10, 12, 25],
bools: [true, false, false]
};
console.log("Original List POJO:", originalListObject);
// 2. Transform it to an Annotations object
const listAnnotations = transformListPOJOToAnnotations(originalListObject);
console.log("Transformed List Annotations:", JSON.stringify(listAnnotations, null, 2));
// 3. Transform it back to a POJO
const reconstructedListObject = transformAnnotationsToListPOJO(listAnnotations);
console.log("Reconstructed List POJO:", reconstructedListObject);
// --- Example Usage ---
// 1. Start with a POJO containing various data types
const originalObject = {
name: "Example Item",
age: 42,
id: 12345,
category: "examples",
isActive: true,
metadata: { version: 2 },
endDate: null
};
console.log("Original POJO:", originalObject);
// 2. Transform it to an Annotations object
const annotationsObject = transformPOJOToKeyValuePairs(originalObject);
console.log("Transformed Annotations:", JSON.stringify(annotationsObject, null, 2));
// 3. Transform it back to a POJO
const reconstructedObject = transformAnnotationsToPOJO(annotationsObject);
console.log("Reconstructed POJO:", reconstructedObject);
let listObj = {
people: [
'Fred Franklin',
'Julie Jackson',
'Susie Smith'
],
places: [
'Grand Juncion',
'New York'
],
favorites: [
5, 10, 12, 25
],
bools: [
true, false, false
]
};
let listAnnot: Annotations =
{
stringAnnotations: [
{key: 'people', value: 'Fred Franklin,Julie Jackson, Susie Smith'},
{key: 'places', value: 'Grand Junction,New York'},
{key: 'favorites', value: '5,10,12,25'},
{key: 'bools', value: 'true,false,false'}
],
numericAnnotations: []
}
let user = {
id: 10,
username: 'fred',
phones: [
{ type: 'cell', number: '123-456-7890' },
{ type: 'home', number: '321-555-1212' }
],
department: 'accounting',
isCurrent: true
};
// Created with indexes username and department
let js_annotation = [
{key: 'value', value: '{"username":"fred","phones":[{"type":"cell","number":"123-456-7890"},{"type":"home","number":"321-555-1212"}],"department":"accounting"}'},
{key: 'isCurrent', value: "true"}, // The true value gets stored as a string in the annotations
{key: 'id', value: 10},
{key: 'username', value: 'fred'},
{key: 'department', value: 'accounting'}
];
const result = prepareJSONObjectForGolem(user, ["id", "username", "department", "isCurrent"]);
console.log(result.dataAsString);
for (let str_annot of result.annotations.stringAnnotations) {
console.log(str_annot);
}
for (let num_annot of result.annotations.numericAnnotations) {
console.log(num_annot);
}
const reconstruct = reconstructObjectFromGolem(result.dataAsString);
console.log(reconstruct);
*/