Files
memorium/routes/api/recipes/create/parseJsonLd.ts
2025-11-12 15:41:30 +01:00

100 lines
2.7 KiB
TypeScript

import recipeSchema, { Recipe } from "@lib/recipeSchema.ts";
export function parseJsonLdToRecipeSchema(jsonLdContent: string) {
try {
let data = JSON.parse(jsonLdContent);
const image = data.image;
// Handle nested data inside `mainEntity`
if (data["mainEntity"]) {
data = data["mainEntity"];
}
// Ensure it's a valid Recipe type
if (
typeof data !== "object" || !data["@type"] || data["@type"] !== "Recipe"
) {
return;
}
const recipeInstructions = Array.isArray(data.recipeInstructions)
? data.recipeInstructions.map((instr: unknown) => {
if (!instr) return "";
if (typeof instr === "string") return instr;
if (typeof instr === "object" && "text" in instr && instr.text) {
return instr.text;
}
return "";
}).filter((instr: string) => instr.trim() !== "")
: [];
// Parse servings
const recipeYield = parseServings(data.recipeYield);
// Parse times
const totalTime = parseDuration(data.totalTime);
// Extract tags
const keywords = data.keywords
? Array.isArray(data.keywords)
? data.keywords
: data.keywords.split(",").map((tag: string) => tag.trim())
: [];
// Build the recipe object
const recipe: Recipe = {
_type: "Recipe",
name: data.name || "Unnamed Recipe",
image: pickImage(image || data.image || ""),
author: {
"_type": "Person",
name: Array.isArray(data.author)
? data.author.map((a: { name: string }) => a.name).join(", ")
: data.author?.name || "",
},
description: data.description || "",
recipeIngredient: data.recipeIngredient,
recipeInstructions,
recipeYield,
totalTime,
keywords,
};
// Validate against the schema
return recipeSchema.parse(recipe);
} catch (error) {
console.log("Invalid JSON-LD content or parsing error:", error);
return undefined;
}
}
function pickImage(images: string | string[]): string {
if (Array.isArray(images)) {
return images[0];
}
return images;
}
function parseServings(servingsData: unknown): number {
if (typeof servingsData === "string") {
const match = servingsData.match(/\d+/);
return match ? parseInt(match[0], 10) : 1;
}
if (typeof servingsData === "number") {
return servingsData;
}
return 1;
}
function parseDuration(duration: string | undefined): number {
if (!duration) return 0;
// Matches ISO 8601 durations (e.g., "PT30M" -> 30 minutes)
const match = duration.match(/PT(?:(\d+)H)?(?:(\d+)M)?/);
const hours = match?.[1] ? parseInt(match[1], 10) : 0;
const minutes = match?.[2] ? parseInt(match[2], 10) : 0;
return hours * 60 + minutes;
}