Skip to content

Commit 4346b34

Browse files
authored
Update object-transform to handle arrays and objects (#18)
* update object-transform to handle arrays and objects * fmt
1 parent 71abd32 commit 4346b34

File tree

3 files changed

+84
-14
lines changed

3 files changed

+84
-14
lines changed

packages/object-transform/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@curatedotfun/object-transform",
3-
"version": "0.0.3",
3+
"version": "0.0.4",
44
"description": "Object transformation plugin for curatedotfun with configurable field mappings",
55
"main": "./dist/index.js",
66
"types": "./dist/index.d.ts",

packages/object-transform/src/__tests__/index.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,4 +158,47 @@ describe("ObjectTransformer", () => {
158158
array: ["[not valid json]"],
159159
});
160160
});
161+
162+
it("should handle nested objects in mappings", async () => {
163+
await transformer.initialize({
164+
mappings: {
165+
title: "{{title}}",
166+
content: "<h2>{{title}}</h2><p>{{summary}}</p>",
167+
author: {
168+
name: "{{username}}",
169+
link: "https://x.com/{{author}}",
170+
},
171+
categories: ["near", "{{tags}}"],
172+
source: {
173+
url: "{{source}}",
174+
title: "twitter",
175+
},
176+
},
177+
});
178+
179+
const result = await transformer.transform({
180+
input: {
181+
title: "Test Post",
182+
summary: "This is a test post",
183+
username: "testuser",
184+
author: "testhandle",
185+
tags: ["test", "example"],
186+
source: "https://twitter.com/testhandle/status/123456789",
187+
},
188+
});
189+
190+
expect(result).toEqual({
191+
title: "Test Post",
192+
content: "<h2>Test Post</h2><p>This is a test post</p>",
193+
author: {
194+
name: "testuser",
195+
link: "https://x.com/testhandle",
196+
},
197+
categories: ["near", "test", "example"],
198+
source: {
199+
url: "https://twitter.com/testhandle/status/123456789",
200+
title: "twitter",
201+
},
202+
});
203+
});
161204
});

packages/object-transform/src/index.ts

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,18 @@ import Mustache from "mustache";
22
import { z } from "zod";
33
import type { TransformerPlugin, ActionArgs } from "@curatedotfun/types";
44

5+
// Disable HTML escaping in Mustache
6+
Mustache.escape = function (text) {
7+
return text;
8+
};
9+
510
// Schema for the configuration
11+
const MappingValueSchema: z.ZodType<any> = z.lazy(() =>
12+
z.union([z.string(), z.array(z.string()), z.record(MappingValueSchema)]),
13+
);
14+
615
const ConfigSchema = z.object({
7-
mappings: z.record(z.union([z.string(), z.array(z.string())])),
16+
mappings: z.record(MappingValueSchema),
817
});
918

1019
type Config = z.infer<typeof ConfigSchema>;
@@ -37,20 +46,22 @@ export default class ObjectTransformer
3746

3847
const output: Record<string, unknown> = {};
3948

40-
for (const [outputField, template] of Object.entries(
41-
this.config.mappings,
42-
)) {
49+
// Recursive function to process mappings, including nested objects
50+
const processMapping = (
51+
template: string | string[] | Record<string, unknown>,
52+
inputData: Record<string, unknown>,
53+
): unknown => {
4354
// Helper function to process template value
4455
const processTemplate = (template: string) => {
45-
const rendered = Mustache.render(template, input);
56+
const rendered = Mustache.render(template, inputData);
4657

4758
// If the template references a field that's an array or object, return it directly
4859
const fieldMatch = template.match(/^\{\{([^}]+)\}\}$/);
4960
if (fieldMatch) {
5061
const field = fieldMatch[1];
5162
const value = field
5263
.split(".")
53-
.reduce((obj: any, key) => obj?.[key], input);
64+
.reduce((obj: any, key) => obj?.[key], inputData);
5465
if (
5566
Array.isArray(value) ||
5667
(typeof value === "object" && value !== null)
@@ -71,10 +82,14 @@ export default class ObjectTransformer
7182
return rendered;
7283
};
7384

74-
// Process the template or array of templates
75-
if (Array.isArray(template)) {
85+
// Process based on template type
86+
if (typeof template === "string") {
87+
const result = processTemplate(template);
88+
// For string templates, preserve empty arrays but convert undefined to empty string
89+
return Array.isArray(result) ? result : (result ?? "");
90+
} else if (Array.isArray(template)) {
7691
const results = template.map(processTemplate);
77-
output[outputField] = results.reduce((acc: unknown[], result) => {
92+
return results.reduce((acc: unknown[], result) => {
7893
if (result === undefined || result === "") {
7994
return acc;
8095
}
@@ -85,11 +100,23 @@ export default class ObjectTransformer
85100
}
86101
return acc;
87102
}, []);
88-
} else {
89-
const result = processTemplate(template);
90-
// For non-array templates, preserve empty arrays but convert undefined to empty string
91-
output[outputField] = Array.isArray(result) ? result : (result ?? "");
103+
} else if (typeof template === "object" && template !== null) {
104+
// Handle nested object
105+
const nestedOutput: Record<string, unknown> = {};
106+
for (const [key, value] of Object.entries(template)) {
107+
nestedOutput[key] = processMapping(value as any, inputData);
108+
}
109+
return nestedOutput;
92110
}
111+
112+
return template;
113+
};
114+
115+
// Process each top-level mapping
116+
for (const [outputField, template] of Object.entries(
117+
this.config.mappings,
118+
)) {
119+
output[outputField] = processMapping(template, input);
93120
}
94121

95122
return output;

0 commit comments

Comments
 (0)