From efe763aebd003d67e5344ff12947cde69e98883c Mon Sep 17 00:00:00 2001 From: MrCheater Date: Thu, 15 Jan 2026 15:19:04 +0300 Subject: [PATCH 1/5] feat: wip --- README.md | 14 ++++- package.json | 2 +- src/commands/postgres/setup.ts | 2 +- src/normalize/normalize-dictionaries.ts | 40 +++++++++++++ src/normalize/normalize.test.ts | 77 +++++++++++++++++++++++++ src/normalize/normalize.ts | 23 +++++--- src/utils/index.ts | 1 + src/utils/is-dictionary.ts | 10 ++++ 8 files changed, 155 insertions(+), 14 deletions(-) create mode 100644 src/normalize/normalize-dictionaries.ts create mode 100644 src/utils/is-dictionary.ts diff --git a/README.md b/README.md index 44afebb..9c0bd22 100644 --- a/README.md +++ b/README.md @@ -49,12 +49,20 @@ Check out the examples/ directory for ready-to-use JSON samples: ## 📦 Installation +Add an `.npmrc` file to your project: + +``` +@analtools:registry=https://npm.pkg.github.com +``` + +Then run one of the following commands: + ```sh -npm install jsonormalize +npm install @analtools/jsonormalize # or -yarn add jsonormalize +yarn add @analtools/jsonormalize # or -pnpm add jsonormalize +pnpm add @analtools/jsonormalize ``` ## 🚀 Quick Start diff --git a/package.json b/package.json index e1690f9..cecc741 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@analtools/jsonormalize", - "version": "0.0.6", + "version": "0.0.8", "description": "JSONormalize — Transform any JSON into a relational database schema. Automatically normalizes nested structures, detects relationships, and generates SQLite migrations. Perfect for rapid prototyping, data migrations, and structured data workflows.", "keywords": [ "json-normalize", diff --git a/src/commands/postgres/setup.ts b/src/commands/postgres/setup.ts index 08c6144..cb07985 100644 --- a/src/commands/postgres/setup.ts +++ b/src/commands/postgres/setup.ts @@ -98,7 +98,7 @@ export async function setup( clientEncoding?: string; fallbackApplicationName?: string; options?: string; - }, + } = {}, ) { const config: ClientConfig | undefined = dbPath === undefined && Object.keys(options).length diff --git a/src/normalize/normalize-dictionaries.ts b/src/normalize/normalize-dictionaries.ts new file mode 100644 index 0000000..9908db7 --- /dev/null +++ b/src/normalize/normalize-dictionaries.ts @@ -0,0 +1,40 @@ +import { isDictionary } from "../utils"; +import type { NormalizedData } from "./types"; + +export function normalizeDictionaries(data: any): NormalizedData { + if (Array.isArray(data)) { + return data; + } else if (isDictionary(data)) { + const types = Array.from( + new Set( + Object.values(data) + .filter((value) => value !== null) + .map((value) => typeof value), + ), + ); + + if (types.length === 1) { + return Object.entries(data).map(([key, value]: [string, any]) => ({ + key, + value, + })); + } else { + const baseRow = Object.fromEntries( + types.map((type) => [`value_${type}`, null]), + ); + return Object.entries(data).map(([key, value]) => ({ + key, + ...baseRow, + ...(value === null + ? {} + : { + [`value_${typeof value}`]: value, + }), + })); + } + } else if (Object(data) === data) { + return [data]; + } else { + return []; + } +} diff --git a/src/normalize/normalize.test.ts b/src/normalize/normalize.test.ts index 00f2498..cc8ad5c 100644 --- a/src/normalize/normalize.test.ts +++ b/src/normalize/normalize.test.ts @@ -3,6 +3,83 @@ import { describe, expect, it } from "vitest"; import { normalize } from "./normalize"; describe("normalize", () => { + it("should normalize dictionary", () => { + expect( + normalize({ + a: "A", + b: "B", + c: null, + }), + ).toEqual([ + { key: "a", value: "A" }, + { key: "b", value: "B" }, + { key: "c", value: null }, + ]); + + expect( + normalize({ + a: 1, + b: null, + c: 3, + }), + ).toEqual([ + { key: "a", value: 1 }, + { key: "b", value: null }, + { key: "c", value: 3 }, + ]); + + expect( + normalize({ + a: null, + b: true, + c: false, + }), + ).toEqual([ + { key: "a", value: null }, + { key: "b", value: true }, + { key: "c", value: false }, + ]); + + expect( + normalize({ + a: 1, + b: "B", + c: true, + d: null, + }), + ).toEqual([ + { key: "a", value_number: 1, value_string: null, value_boolean: null }, + { key: "b", value_number: null, value_string: "B", value_boolean: null }, + { key: "c", value_number: null, value_string: null, value_boolean: true }, + { key: "d", value_number: null, value_string: null, value_boolean: null }, + ]); + + expect( + normalize({ + obj: { + sub: { + a: 1, + b: 2, + c: 3, + }, + }, + }), + ).toEqual([ + { + key: "obj_sub_a", + value: 1, + }, + { + key: "obj_sub_b", + value: 2, + }, + { + key: "obj_sub_c", + value: 3, + }, + ]); + }); + it("should flatten nested objects with localization and arrays", () => { expect( normalize([ diff --git a/src/normalize/normalize.ts b/src/normalize/normalize.ts index 71886fb..55b8bd8 100644 --- a/src/normalize/normalize.ts +++ b/src/normalize/normalize.ts @@ -1,18 +1,23 @@ import { normalizePrimitiveArrays } from "./normalize-array-of-arrays"; import { normalizeArrayOfPrimitives } from "./normalize-array-of-primitives"; import { normalizeDeepObjects } from "./normalize-deep-objects"; +import { normalizeDictionaries } from "./normalize-dictionaries"; import { normalizeLocalization } from "./normalize-localization"; import type { NormalizedData } from "./types"; -export function normalize(data: unknown[]): NormalizedData { - /* replace {a:{b:'c'}} to {'a_b':'c'} */ - return normalizeDeepObjects( - /* replace [[...],[...]] to [{items:[...]},{items:[...]}] */ - normalizePrimitiveArrays( - /* replace [1,2,3] to [{value:1},{value:2},{value:3}] */ - normalizeArrayOfPrimitives( - /* replace { en: string, zh: string, ... } to { lang: string, text: string }[]*/ - normalizeLocalization(data), +export function normalize( + data: unknown[] | Record, +): NormalizedData { + return normalizeDictionaries( + /* replace {a:{b:'c'}} to {'a_b':'c'} */ + normalizeDeepObjects( + /* replace [[...],[...]] to [{items:[...]},{items:[...]}] */ + normalizePrimitiveArrays( + /* replace [1,2,3] to [{value:1},{value:2},{value:3}] */ + normalizeArrayOfPrimitives( + /* replace { en: string, zh: string, ... } to { lang: string, text: string }[]*/ + normalizeLocalization(data), + ), ), ), ); diff --git a/src/utils/index.ts b/src/utils/index.ts index b7c814e..fd7a222 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,5 +1,6 @@ export * from "./is-array-of-arrays"; export * from "./is-array-of-primitives"; +export * from "./is-dictionary"; export * from "./is-localization-object"; export * from "./is-url"; export * from "./snake-case"; diff --git a/src/utils/is-dictionary.ts b/src/utils/is-dictionary.ts new file mode 100644 index 0000000..33131f5 --- /dev/null +++ b/src/utils/is-dictionary.ts @@ -0,0 +1,10 @@ +const simpleTypes = ["string", "number", "boolean"]; + +export function isDictionary(data: any) { + return ( + Object(data) === data || + Object.values(data).every( + (value) => value === null || simpleTypes.includes(typeof value), + ) + ); +} From 501ff995eff273905bf9df11b789c048e6b3c3b5 Mon Sep 17 00:00:00 2001 From: MrCheater Date: Thu, 15 Jan 2026 15:29:23 +0300 Subject: [PATCH 2/5] feat: wip --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index deec57e..5f81f4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@analtools/jsonormalize", - "version": "0.0.6", + "version": "0.0.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@analtools/jsonormalize", - "version": "0.0.6", + "version": "0.0.8", "license": "MIT", "dependencies": { "@electric-sql/pglite": "^0.3.14", From ded87412a14ed8a075dd5338bf82a2bba6a1825d Mon Sep 17 00:00:00 2001 From: MrCheater Date: Thu, 15 Jan 2026 16:42:13 +0300 Subject: [PATCH 3/5] feat: wip --- package-lock.json | 4 ++-- package.json | 2 +- src/commands/prepare.ts | 3 +-- src/normalize/normalize.ts | 4 +--- src/postgres/create-migrations.ts | 2 +- src/postgres/setup-tables.ts | 2 +- src/sqlite/create-migrations.ts | 2 +- src/sqlite/setup-tables.ts | 2 +- 8 files changed, 9 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5f81f4e..b5ac614 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@analtools/jsonormalize", - "version": "0.0.8", + "version": "0.0.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@analtools/jsonormalize", - "version": "0.0.8", + "version": "0.0.9", "license": "MIT", "dependencies": { "@electric-sql/pglite": "^0.3.14", diff --git a/package.json b/package.json index cecc741..472b8bc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@analtools/jsonormalize", - "version": "0.0.8", + "version": "0.0.9", "description": "JSONormalize — Transform any JSON into a relational database schema. Automatically normalizes nested structures, detects relationships, and generates SQLite migrations. Perfect for rapid prototyping, data migrations, and structured data workflows.", "keywords": [ "json-normalize", diff --git a/src/commands/prepare.ts b/src/commands/prepare.ts index a287476..0951422 100644 --- a/src/commands/prepare.ts +++ b/src/commands/prepare.ts @@ -19,8 +19,7 @@ async function getRawData(jsonPath: string): Promise { } export async function prepare(jsonPath: string) { - const rawData = await getRawData(jsonPath); - const data: unknown[] = Array.isArray(rawData) ? rawData : [rawData]; + const data = await getRawData(jsonPath); const jsonPathNameWithoutExt = path.basename( jsonPath, diff --git a/src/normalize/normalize.ts b/src/normalize/normalize.ts index 55b8bd8..862de3a 100644 --- a/src/normalize/normalize.ts +++ b/src/normalize/normalize.ts @@ -5,9 +5,7 @@ import { normalizeDictionaries } from "./normalize-dictionaries"; import { normalizeLocalization } from "./normalize-localization"; import type { NormalizedData } from "./types"; -export function normalize( - data: unknown[] | Record, -): NormalizedData { +export function normalize(data: unknown): NormalizedData { return normalizeDictionaries( /* replace {a:{b:'c'}} to {'a_b':'c'} */ normalizeDeepObjects( diff --git a/src/postgres/create-migrations.ts b/src/postgres/create-migrations.ts index ea2ce48..ecb5994 100644 --- a/src/postgres/create-migrations.ts +++ b/src/postgres/create-migrations.ts @@ -8,7 +8,7 @@ export function createMigrations({ data, }: { prefix: string; - data: unknown[]; + data: unknown; }) { const tables = createRelationalStructure(prefix, normalize(data)); diff --git a/src/postgres/setup-tables.ts b/src/postgres/setup-tables.ts index ac9aba1..d2e30c9 100644 --- a/src/postgres/setup-tables.ts +++ b/src/postgres/setup-tables.ts @@ -12,7 +12,7 @@ export async function setupTables({ config?: ClientConfig; path?: string; prefix: string; - data: unknown[]; + data: unknown; }) { const { initialMigration, dataMigration } = createMigrations({ prefix, diff --git a/src/sqlite/create-migrations.ts b/src/sqlite/create-migrations.ts index ea2ce48..ecb5994 100644 --- a/src/sqlite/create-migrations.ts +++ b/src/sqlite/create-migrations.ts @@ -8,7 +8,7 @@ export function createMigrations({ data, }: { prefix: string; - data: unknown[]; + data: unknown; }) { const tables = createRelationalStructure(prefix, normalize(data)); diff --git a/src/sqlite/setup-tables.ts b/src/sqlite/setup-tables.ts index 8137180..2c60026 100644 --- a/src/sqlite/setup-tables.ts +++ b/src/sqlite/setup-tables.ts @@ -9,7 +9,7 @@ export function setupTables({ }: { path?: string; prefix: string; - data: unknown[]; + data: unknown; }) { const { initialMigration, dataMigration } = createMigrations({ prefix, From ad0bd20a1ae2de3e19f73f4546ceb127044f1016 Mon Sep 17 00:00:00 2001 From: MrCheater Date: Fri, 16 Jan 2026 11:48:14 +0300 Subject: [PATCH 4/5] feat: wip --- package-lock.json | 4 ++-- package.json | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index b5ac614..37619e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@analtools/jsonormalize", - "version": "0.0.9", + "version": "0.0.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@analtools/jsonormalize", - "version": "0.0.9", + "version": "0.0.11", "license": "MIT", "dependencies": { "@electric-sql/pglite": "^0.3.14", diff --git a/package.json b/package.json index 472b8bc..3bb6b4a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@analtools/jsonormalize", - "version": "0.0.9", + "version": "0.0.11", "description": "JSONormalize — Transform any JSON into a relational database schema. Automatically normalizes nested structures, detects relationships, and generates SQLite migrations. Perfect for rapid prototyping, data migrations, and structured data workflows.", "keywords": [ "json-normalize", @@ -33,7 +33,8 @@ "data-formatting" ], "publishConfig": { - "registry": "https://npm.pkg.github.com" + "registry": "https://npm.pkg.github.com", + "access": "publics" }, "repository": { "type": "git", @@ -77,6 +78,7 @@ "node-fetch": "2.7.0" }, "scripts": { + "postinstall": "npm run build", "prettier:check": "prettier '**/*.{svg,yml,js,jsx,ts,tsx,html,md,css,json}' --check", "prettier": "prettier '**/*.{svg,yml,js,jsx,ts,tsx,html,md,css,json}' --write", "lint": "eslint --quiet .", From 3bb34f146b9e38b1dcf97a85dfcca0cd9dd3bcc1 Mon Sep 17 00:00:00 2001 From: MrCheater Date: Fri, 16 Jan 2026 13:37:40 +0300 Subject: [PATCH 5/5] feat: wip --- package-lock.json | 4 ++-- package.json | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 37619e9..4b886f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@analtools/jsonormalize", - "version": "0.0.11", + "version": "0.0.12", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@analtools/jsonormalize", - "version": "0.0.11", + "version": "0.0.12", "license": "MIT", "dependencies": { "@electric-sql/pglite": "^0.3.14", diff --git a/package.json b/package.json index 3bb6b4a..2a06438 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@analtools/jsonormalize", - "version": "0.0.11", + "version": "0.0.12", "description": "JSONormalize — Transform any JSON into a relational database schema. Automatically normalizes nested structures, detects relationships, and generates SQLite migrations. Perfect for rapid prototyping, data migrations, and structured data workflows.", "keywords": [ "json-normalize", @@ -78,7 +78,6 @@ "node-fetch": "2.7.0" }, "scripts": { - "postinstall": "npm run build", "prettier:check": "prettier '**/*.{svg,yml,js,jsx,ts,tsx,html,md,css,json}' --check", "prettier": "prettier '**/*.{svg,yml,js,jsx,ts,tsx,html,md,css,json}' --write", "lint": "eslint --quiet .",