Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 37 additions & 15 deletions src/localStorage.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,33 @@ const TOS_RESTRICTED_ATTRIBUTES = [
"vehicleWaypoints",
];

/**
* Recursively sorts the keys of an object or objects within an array,
* ensuring a consistent order for display and comparison.
* @param {*} data The object or array to sort.
* @returns {*} The sorted object or array.
*/
export function sortObjectKeysRecursively(data) {
const _sort = (obj) => {
if (obj === null || typeof obj !== "object") {
return obj;
}

if (Array.isArray(obj)) {
return obj.map(_sort);
}

return Object.keys(obj)
.sort()
.reduce((sorted, key) => {
sorted[key] = _sort(obj[key]);
return sorted;
}, {});
};

return _sort(data);
}

async function openDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME, 1);
Expand All @@ -32,8 +59,10 @@ export async function uploadFile(file, index) {
console.log(`Importing file: ${file.name}`);
let parsedData;
if (file.name.endsWith(".zip")) {
log("uploadFile: Processing ZIP file.");
parsedData = await processZipFile(file);
} else if (file.name.endsWith(".json")) {
log("uploadFile: Processing JSON file.");
parsedData = await processJsonFile(file);
} else {
throw new Error("Unsupported file format. Please upload a ZIP or JSON file.");
Expand Down Expand Up @@ -128,19 +157,6 @@ async function processJsonFile(file) {
export function parseJsonContent(content) {
log("Parsing JSON content");

const sortObjectKeys = (obj) => {
if (obj === null || typeof obj !== "object" || Array.isArray(obj)) {
return obj;
}

return Object.keys(obj)
.sort()
.reduce((sorted, key) => {
sorted[key] = sortObjectKeys(obj[key]);
return sorted;
}, {});
};

const processJsonObject = (obj) => {
if (obj === null || typeof obj !== "object") return obj;
if (Array.isArray(obj)) return obj.map(processJsonObject);
Expand Down Expand Up @@ -186,14 +202,14 @@ export function parseJsonContent(content) {
const parsed = JSON.parse(content);
const processedData = processJsonObject(parsed);
log("Processed JSON data: removed underscores, flattened value objects, and pruned null/undefined fields");
return sortObjectKeys(processedData);
return sortObjectKeysRecursively(processedData);
} catch (error) {
log("Initial JSON parsing failed, attempting to wrap in array");
try {
const parsed = JSON.parse(`[${content}]`);
const processedData = processJsonObject(parsed);
log("Processed JSON data in array format");
return sortObjectKeys(processedData);
return sortObjectKeysRecursively(processedData);
} catch (innerError) {
console.error("JSON parsing error:", innerError);
throw new Error(`Invalid JSON content: ${innerError.message}`);
Expand Down Expand Up @@ -271,6 +287,12 @@ export function ensureCorrectFormat(data) {
return true;
});

mergedLogs.forEach((row) => {
if (row.jsonPayload) {
row.jsonPayload = sortObjectKeysRecursively(row.jsonPayload);
}
});

// Determine the solution type based on the presence of _delivery_vehicle logs
const isLMFS = mergedLogs.some((row) => row.logName?.includes("_delivery_vehicle"));
const solutionType = isLMFS ? "LMFS" : "ODRD";
Expand Down
42 changes: 34 additions & 8 deletions src/localStorage.test.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
// src/localStorage.test.js

import fs from "fs";
import { parseJsonContent, removeEmptyObjects, ensureCorrectFormat } from "./localStorage";
import { parseJsonContent, removeEmptyObjects, ensureCorrectFormat, sortObjectKeysRecursively } from "./localStorage";

// Helper function to load test data
function loadTestData(filename) {
return JSON.parse(fs.readFileSync(`./datasets/${filename}`));
}

test("sortObjectKeysRecursively sorts object keys recursively but preserves array order", () => {
const unsorted = {
c: 3,
a: 1,
b: [{ z: "last", x: "first" }, { y: "middle" }],
};

const expected = {
a: 1,
b: [{ x: "first", z: "last" }, { y: "middle" }],
c: 3,
};

const sorted = sortObjectKeysRecursively(unsorted);

// Using JSON.stringify provides a simple and effective way to verify
// both the structure and the key order of the entire object.
expect(JSON.stringify(sorted)).toBe(JSON.stringify(expected));
});

test("parseJsonContent handles valid JSON", () => {
const validJson = JSON.stringify({ test: "data" });
const result = parseJsonContent(validJson);
Expand All @@ -31,22 +50,28 @@ test("parseJsonContent handles JSON array", () => {
});

test("parseJsonContent throws error for invalid JSON", () => {
// Temporarily spy on console.error and replace it with a function that does nothing.
const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => {});

const invalidJson = "{invalid}";
expect(() => parseJsonContent(invalidJson)).toThrow("Invalid JSON content");

consoleErrorSpy.mockRestore();
});

test("parseJsonContent removes underscores from keys", () => {
test("parseJsonContent removes underscores from keys and sorts them", () => {
const snakeCaseJson = JSON.stringify({
snake_case_key: "value",
normal_key: "value2",
another_key: "value2",
});

const result = parseJsonContent(snakeCaseJson);

expect(result).toHaveProperty("snakecasekey", "value");
expect(result).toHaveProperty("normalkey", "value2");
expect(result).toHaveProperty("anotherkey", "value2");
expect(result).not.toHaveProperty("snake_case_key");
expect(result).not.toHaveProperty("normal_key");
expect(result).not.toHaveProperty("another_key");
expect(Object.keys(result)).toEqual(["anotherkey", "snakecasekey"]);
});

test("parseJsonContent removes underscores from deeply nested object keys", () => {
Expand All @@ -65,17 +90,18 @@ test("parseJsonContent removes underscores from deeply nested object keys", () =
});

// New tests for value object flattening
test("parseJsonContent flattens objects with a single 'value' property", () => {
test("parseJsonContent flattens value objects and sorts keys", () => {
const valueObjectJson = JSON.stringify({
normalKey: "normal",
valueObject: { value: "flattened" },
normalKey: "normal",
});

const result = parseJsonContent(valueObjectJson);

expect(result.normalKey).toBe("normal");
expect(result.valueObject).toBe("flattened");
expect(typeof result.valueObject).toBe("string");
expect(Object.keys(result)).toEqual(["normalKey", "valueObject"]);
});

test("parseJsonContent flattens nested objects with a single 'value' property", () => {
Expand Down
Loading