Skip to content

Commit ae4140f

Browse files
authored
feat: normalize dictionaries (#3)
1 parent 0dbe7f4 commit ae4140f

14 files changed

Lines changed: 162 additions & 23 deletions

README.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,20 @@ Check out the examples/ directory for ready-to-use JSON samples:
4949

5050
## 📦 Installation
5151

52+
Add an `.npmrc` file to your project:
53+
54+
```
55+
@analtools:registry=https://npm.pkg.github.com
56+
```
57+
58+
Then run one of the following commands:
59+
5260
```sh
53-
npm install jsonormalize
61+
npm install @analtools/jsonormalize
5462
# or
55-
yarn add jsonormalize
63+
yarn add @analtools/jsonormalize
5664
# or
57-
pnpm add jsonormalize
65+
pnpm add @analtools/jsonormalize
5866
```
5967

6068
## 🚀 Quick Start

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@analtools/jsonormalize",
3-
"version": "0.0.6",
3+
"version": "0.0.12",
44
"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.",
55
"keywords": [
66
"json-normalize",
@@ -33,7 +33,8 @@
3333
"data-formatting"
3434
],
3535
"publishConfig": {
36-
"registry": "https://npm.pkg.github.com"
36+
"registry": "https://npm.pkg.github.com",
37+
"access": "publics"
3738
},
3839
"repository": {
3940
"type": "git",

src/commands/postgres/setup.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export async function setup(
9898
clientEncoding?: string;
9999
fallbackApplicationName?: string;
100100
options?: string;
101-
},
101+
} = {},
102102
) {
103103
const config: ClientConfig | undefined =
104104
dbPath === undefined && Object.keys(options).length

src/commands/prepare.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ async function getRawData(jsonPath: string): Promise<unknown> {
1919
}
2020

2121
export async function prepare(jsonPath: string) {
22-
const rawData = await getRawData(jsonPath);
23-
const data: unknown[] = Array.isArray(rawData) ? rawData : [rawData];
22+
const data = await getRawData(jsonPath);
2423

2524
const jsonPathNameWithoutExt = path.basename(
2625
jsonPath,
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { isDictionary } from "../utils";
2+
import type { NormalizedData } from "./types";
3+
4+
export function normalizeDictionaries(data: any): NormalizedData {
5+
if (Array.isArray(data)) {
6+
return data;
7+
} else if (isDictionary(data)) {
8+
const types = Array.from(
9+
new Set(
10+
Object.values(data)
11+
.filter((value) => value !== null)
12+
.map((value) => typeof value),
13+
),
14+
);
15+
16+
if (types.length === 1) {
17+
return Object.entries(data).map(([key, value]: [string, any]) => ({
18+
key,
19+
value,
20+
}));
21+
} else {
22+
const baseRow = Object.fromEntries(
23+
types.map((type) => [`value_${type}`, null]),
24+
);
25+
return Object.entries(data).map(([key, value]) => ({
26+
key,
27+
...baseRow,
28+
...(value === null
29+
? {}
30+
: {
31+
[`value_${typeof value}`]: value,
32+
}),
33+
}));
34+
}
35+
} else if (Object(data) === data) {
36+
return [data];
37+
} else {
38+
return [];
39+
}
40+
}

src/normalize/normalize.test.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,83 @@ import { describe, expect, it } from "vitest";
33
import { normalize } from "./normalize";
44

55
describe("normalize", () => {
6+
it("should normalize dictionary", () => {
7+
expect(
8+
normalize({
9+
a: "A",
10+
b: "B",
11+
c: null,
12+
}),
13+
).toEqual([
14+
{ key: "a", value: "A" },
15+
{ key: "b", value: "B" },
16+
{ key: "c", value: null },
17+
]);
18+
19+
expect(
20+
normalize({
21+
a: 1,
22+
b: null,
23+
c: 3,
24+
}),
25+
).toEqual([
26+
{ key: "a", value: 1 },
27+
{ key: "b", value: null },
28+
{ key: "c", value: 3 },
29+
]);
30+
31+
expect(
32+
normalize({
33+
a: null,
34+
b: true,
35+
c: false,
36+
}),
37+
).toEqual([
38+
{ key: "a", value: null },
39+
{ key: "b", value: true },
40+
{ key: "c", value: false },
41+
]);
42+
43+
expect(
44+
normalize({
45+
a: 1,
46+
b: "B",
47+
c: true,
48+
d: null,
49+
}),
50+
).toEqual([
51+
{ key: "a", value_number: 1, value_string: null, value_boolean: null },
52+
{ key: "b", value_number: null, value_string: "B", value_boolean: null },
53+
{ key: "c", value_number: null, value_string: null, value_boolean: true },
54+
{ key: "d", value_number: null, value_string: null, value_boolean: null },
55+
]);
56+
57+
expect(
58+
normalize({
59+
obj: {
60+
sub: {
61+
a: 1,
62+
b: 2,
63+
c: 3,
64+
},
65+
},
66+
}),
67+
).toEqual([
68+
{
69+
key: "obj_sub_a",
70+
value: 1,
71+
},
72+
{
73+
key: "obj_sub_b",
74+
value: 2,
75+
},
76+
{
77+
key: "obj_sub_c",
78+
value: 3,
79+
},
80+
]);
81+
});
82+
683
it("should flatten nested objects with localization and arrays", () => {
784
expect(
885
normalize([

src/normalize/normalize.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
import { normalizePrimitiveArrays } from "./normalize-array-of-arrays";
22
import { normalizeArrayOfPrimitives } from "./normalize-array-of-primitives";
33
import { normalizeDeepObjects } from "./normalize-deep-objects";
4+
import { normalizeDictionaries } from "./normalize-dictionaries";
45
import { normalizeLocalization } from "./normalize-localization";
56
import type { NormalizedData } from "./types";
67

7-
export function normalize(data: unknown[]): NormalizedData {
8-
/* replace {a:{b:'c'}} to {'a_b':'c'} */
9-
return normalizeDeepObjects(
10-
/* replace [[...],[...]] to [{items:[...]},{items:[...]}] */
11-
normalizePrimitiveArrays(
12-
/* replace [1,2,3] to [{value:1},{value:2},{value:3}] */
13-
normalizeArrayOfPrimitives(
14-
/* replace { en: string, zh: string, ... } to { lang: string, text: string }[]*/
15-
normalizeLocalization(data),
8+
export function normalize(data: unknown): NormalizedData {
9+
return normalizeDictionaries(
10+
/* replace {a:{b:'c'}} to {'a_b':'c'} */
11+
normalizeDeepObjects(
12+
/* replace [[...],[...]] to [{items:[...]},{items:[...]}] */
13+
normalizePrimitiveArrays(
14+
/* replace [1,2,3] to [{value:1},{value:2},{value:3}] */
15+
normalizeArrayOfPrimitives(
16+
/* replace { en: string, zh: string, ... } to { lang: string, text: string }[]*/
17+
normalizeLocalization(data),
18+
),
1619
),
1720
),
1821
);

src/postgres/create-migrations.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export function createMigrations({
88
data,
99
}: {
1010
prefix: string;
11-
data: unknown[];
11+
data: unknown;
1212
}) {
1313
const tables = createRelationalStructure(prefix, normalize(data));
1414

src/postgres/setup-tables.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export async function setupTables({
1212
config?: ClientConfig;
1313
path?: string;
1414
prefix: string;
15-
data: unknown[];
15+
data: unknown;
1616
}) {
1717
const { initialMigration, dataMigration } = createMigrations({
1818
prefix,

0 commit comments

Comments
 (0)