Skip to content

Commit

Permalink
TS - add flat, flatten, form, formparse, isnull
Browse files Browse the repository at this point in the history
  • Loading branch information
elisherer committed Aug 5, 2024
1 parent 374332d commit 238434e
Show file tree
Hide file tree
Showing 18 changed files with 492 additions and 41 deletions.
2 changes: 2 additions & 0 deletions javascript/json-transform/src/JsonHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const JSONPATH_ROOT = "$",
JSONPATH_ALT_PREFIX_ESC = "\\#";

const isNullOrUndefined = (value: any): value is null | undefined => value == null || typeof value === "undefined";
const isMap = (value: any): value is Record<string, any> => value && typeof value === "object" && !Array.isArray(value);

const getAsString = (value: any): null | string => {
if (isNullOrUndefined(value)) {
Expand Down Expand Up @@ -189,6 +190,7 @@ const isEqual = (value: any, other: any): boolean => {

export {
isNullOrUndefined,
isMap,
createPayloadResolver,
getAsString,
compareTo,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { describe, test } from "vitest";
import { assertTransformation } from "../BaseTransformationTest";

describe("TransformerFunctionFlat", () => {
test("object", async () => {
const arr = ["a", "b", "c"];
const arr2 = ["d", "e", "f"];
const arr3 = [arr, arr2];
const flatCombined = arr3.flatMap(x => x);
const flatArr = arr.slice();
await assertTransformation(
arr3,
{
$$flat: ["$[0]", "$[1]"],
},
flatCombined,
);
await assertTransformation(
arr3,
{
$$flat: ["$[0]", "$.pointingToNowhere"],
},
flatArr,
);
await assertTransformation(
arr3,
{
$$flat: [
["a", "b", "c"],
["d", "e", "f"],
],
},
flatCombined,
);
await assertTransformation(
arr,
{
$$flat: [["a", "b", "c"], []],
},
flatArr,
);
await assertTransformation(
arr,
{
$$flat: [["a", "b", "c"], null],
},
flatArr,
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { describe, test } from "vitest";
import { assertTransformation } from "../BaseTransformationTest";

describe("TransformerFunctionFlat", () => {
test("object", async () => {
const arr = { value: "bbb" };
await assertTransformation(
arr,
{
$$flatten: { a: { a1: 123, a2: [1, 2, 3, { c: true }] }, b: "$.value" },
array_prefix: "\\$",
},
{ "a.a1": 123, "a.a2.$0": 1, "a.a2.$1": 2, "a.a2.$2": 3, "a.a2.$3.c": true, b: "bbb" },
);

await assertTransformation(
arr,
{
$$flatten: { a: { a1: 123, a2: [1, 2, 3, { c: true }] }, b: "$.value" },
prefix: "xxx",
array_prefix: "",
},
{ "xxx.a.a1": 123, "xxx.a.a2.0": 1, "xxx.a.a2.1": 2, "xxx.a.a2.2": 3, "xxx.a.a2.3.c": true, "xxx.b": "bbb" },
);

await assertTransformation(
arr,
{
$$flatten: { a: { a1: 123, a2: [1, 2, 3, { c: true }] }, b: "$.value" },
prefix: "xxx",
array_prefix: "#null",
},
{ "xxx.a.a1": 123, "xxx.a.a2": [1, 2, 3, { c: true }], "xxx.b": "bbb" },
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { describe, test } from "vitest";
import { assertTransformation } from "../BaseTransformationTest";

describe("TransformerFunctionCsv", () => {
test("inline", async () => {
await assertTransformation(
{
a: 1,
b: "B",
c: true,
},
"$$form:$",
"a=1&b=B&c=true",
);

// arrays
await assertTransformation(
{
a: [1, 2],
c: true,
},
"$$form:$",
"a=1&a=2&c=true",
);
});

test("object", async () => {
await assertTransformation(
{
a: 1,
b: "B",
c: true,
},
{ $$form: "$" },
"a=1&b=B&c=true",
);

// arrays
await assertTransformation(
{
a: [1, 2],
c: true,
},
{ $$form: "$" },
"a=1&a=2&c=true",
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { describe, test } from "vitest";
import { assertTransformation } from "../BaseTransformationTest";

describe("TransformerFunctionCsv", () => {
test("inline", async () => {
await assertTransformation("a=1&b=B&c", "$$formparse:$", {
a: "1",
a$$: ["1"],
b: "B",
b$$: ["B"],
c: "true",
c$$: ["true"],
});

// arrays
await assertTransformation("a=1&a=2", "$$formparse:$", {
a: "1",
a$$: ["1", "2"],
});
});

test("object", async () => {
await assertTransformation(
"a=1&b=B&c",
{ $$formparse: "$" },
{
a: "1",
a$$: ["1"],
b: "B",
b$$: ["B"],
c: "true",
c$$: ["true"],
},
);

// arrays
await assertTransformation(
"a=1&a=2",
{ $$formparse: "$" },
{
a: "1",
a$$: ["1", "2"],
},
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { describe, test } from "vitest";
import { assertTransformation } from "../BaseTransformationTest";

describe("TransformerFunctionIsNull", () => {
test("nullTest", async () => {
await assertTransformation(null, "$$isnull:$", true);
await assertTransformation(undefined, "$$isnull:$", true);
await assertTransformation(0, "$$isnull():$", false);
await assertTransformation("", "$$isnull:$", false);
await assertTransformation(false, "$$isnull:$", false);
});
});
4 changes: 2 additions & 2 deletions javascript/json-transform/src/formats/FormatDeserializer.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export interface FormatDeserializer {
deserialize(input: string): Record<string, any>;
}
deserialize(input: string | null): any;
}
4 changes: 2 additions & 2 deletions javascript/json-transform/src/formats/FormatSerializer.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export interface FormatSerializer {
serialize(payload: any): string;
}
serialize(payload: any): string | null;
}
55 changes: 34 additions & 21 deletions javascript/json-transform/src/formats/csv/CsvFormat.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {getAsString, isNullOrUndefined} from "../../JsonHelpers";
import {FormatSerializer} from "../FormatSerializer";
import {FormatDeserializer} from "../FormatDeserializer";
import { getAsString, isMap, isNullOrUndefined } from "../../JsonHelpers";
import { FormatSerializer } from "../FormatSerializer";
import { FormatDeserializer } from "../FormatDeserializer";

const MIN_SUPPLEMENTARY_CODE_POINT = 0x010000;
function charCount(codePoint: number) {
Expand All @@ -10,19 +10,24 @@ function charCount(codePoint: number) {
class CsvFormat implements FormatSerializer, FormatDeserializer {
private static readonly COMMA = ",";
private static readonly DEFAULT_SEPARATOR = CsvFormat.COMMA;
private static readonly DOUBLE_QUOTES = "\"";
private static readonly EMBEDDED_DOUBLE_QUOTES = "\"\"";
private static readonly DOUBLE_QUOTES = '"';
private static readonly EMBEDDED_DOUBLE_QUOTES = '""';
private static readonly NEW_LINE_UNIX = "\n";
private static readonly LINE_FEED = '\n'.codePointAt(0);
private static readonly CARRIAGE_RETURN = '\r'.codePointAt(0);
private static readonly LINE_FEED = "\n".codePointAt(0);
private static readonly CARRIAGE_RETURN = "\r".codePointAt(0);
private static readonly NEW_LINE_WINDOWS = "\r\n";

private readonly names?: string[];
private readonly noHeaders: boolean;
private readonly forceQuote: boolean;
private readonly separator: string;

constructor(names?: string[] | null, noHeaders?: boolean | null, forceQuote?: boolean | null, separator?: string | null) {
constructor(
names?: string[] | null,
noHeaders?: boolean | null,
forceQuote?: boolean | null,
separator?: string | null,
) {
this.names = names ?? undefined;
this.noHeaders = isNullOrUndefined(noHeaders) ? false : noHeaders;
this.forceQuote = isNullOrUndefined(forceQuote) ? false : forceQuote;
Expand All @@ -36,15 +41,17 @@ class CsvFormat implements FormatSerializer, FormatDeserializer {
} else {
value = getAsString(val) ?? "";
}
if (this.forceQuote ||
if (
this.forceQuote ||
value.includes(CsvFormat.COMMA) ||
value.includes(CsvFormat.DOUBLE_QUOTES) ||
value.includes(CsvFormat.NEW_LINE_UNIX) ||
value.includes(CsvFormat.NEW_LINE_WINDOWS) ||
value.startsWith(" ") ||
value.endsWith(" ")) {
value.endsWith(" ")
) {
sb.append(CsvFormat.DOUBLE_QUOTES);
sb.append(value.replace(new RegExp(CsvFormat.DOUBLE_QUOTES, 'g'), CsvFormat.EMBEDDED_DOUBLE_QUOTES));
sb.append(value.replace(new RegExp(CsvFormat.DOUBLE_QUOTES, "g"), CsvFormat.EMBEDDED_DOUBLE_QUOTES));
sb.append(CsvFormat.DOUBLE_QUOTES);
} else {
sb.append(value);
Expand All @@ -67,7 +74,7 @@ class CsvFormat implements FormatSerializer, FormatDeserializer {

private appendRow(sb: StringBuilder, names: string[] | null | undefined, value: any): void {
if (!Array.isArray(value) && names) {
if (typeof value !== 'object' || value === null) return;
if (typeof value !== "object" || value === null) return;
let first = true;
for (const name of names) {
if (!first) {
Expand All @@ -91,15 +98,15 @@ class CsvFormat implements FormatSerializer, FormatDeserializer {
sb.append("\n");
}

serialize(payload: any): string {
serialize(payload: any): string | null {
const sb = new StringBuilder();
let headers = this.names;
if (headers) {
this.appendHeaders(sb, headers);
}

if (Array.isArray(payload)) {
if (!headers && payload.length > 0 && typeof payload[0] === 'object' && !Array.isArray(payload[0])) {
if (!headers && payload.length > 0 && isMap(payload[0])) {
headers = Object.keys(payload[0]);
this.appendHeaders(sb, headers);
}
Expand All @@ -124,11 +131,14 @@ class CsvFormat implements FormatSerializer, FormatDeserializer {
return;
}
if (!isNullOrUndefined(context.names)) {
const item : Record<string, any> = {};
const item: Record<string, any> = {};
let i = 0;
for (i = 0; i < context.names.length; i++) {
const name = getAsString(context.names[i]) ?? "";
if ((context.extractNames === null || Object.prototype.hasOwnProperty.call(context.extractNames, name)) && values.length > i) {
if (
(context.extractNames === null || Object.prototype.hasOwnProperty.call(context.extractNames, name)) &&
values.length > i
) {
item[name] = values[i];
}
}
Expand All @@ -141,8 +151,11 @@ class CsvFormat implements FormatSerializer, FormatDeserializer {
}
}

deserialize(input: string): any {
const result : any[] = [];
deserialize(input: string | null): any {
if (input === null) {
return null;
}
const result: any[] = [];
const context = new CsvParserContext();
if (this.noHeaders && !isNullOrUndefined(this.names)) {
const names: string[] = [];
Expand All @@ -159,7 +172,7 @@ class CsvFormat implements FormatSerializer, FormatDeserializer {
while (offset < len) {
const cur = input.codePointAt(offset) as number;
const curSize = charCount(cur);
const next = offset + curSize < len ? input.codePointAt(offset + curSize) as number : -1;
const next = offset + curSize < len ? (input.codePointAt(offset + curSize) as number) : -1;
const curAndNextSize = curSize + charCount(next);

if (cur === this.separator.codePointAt(0)) {
Expand Down Expand Up @@ -230,7 +243,7 @@ class StringBuilder {
}

public toString(): string {
return this.strings.join('');
return this.strings.join("");
}

public clear(): void {
Expand All @@ -242,4 +255,4 @@ class StringBuilder {
}
}

export default CsvFormat;
export default CsvFormat;
Loading

0 comments on commit 238434e

Please sign in to comment.