import recipeSchema from "@lib/recipeSchema.ts"; import { parseIngredients } from "@lib/parseIngredient.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; } // Map and parse ingredients into the new schema const ingredients = parseIngredients( data?.recipeIngredient?.join("\n") || "", ); const instructions = Array.isArray(data.recipeInstructions) ? data.recipeInstructions.map((instr) => { if (typeof instr === "string") return instr; if (typeof instr === "object" && instr.text) return instr.text; return ""; }).filter((instr) => instr.trim() !== "") : []; // Parse servings const servings = parseServings(data.recipeYield); // Parse times const prepTime = parseDuration(data.prepTime); const cookTime = parseDuration(data.cookTime); const totalTime = parseDuration(data.totalTime); // Extract tags const tags = data.keywords ? Array.isArray(data.keywords) ? data.keywords : data.keywords.split(",").map((tag: string) => tag.trim()) : []; // Build the recipe object const recipe = { title: data.name || "Unnamed Recipe", image: pickImage(image || data.image || ""), author: Array.isArray(data.author) ? data.author.map((a: any) => a.name).join(", ") : data.author?.name || "", description: data.description || "", ingredients, instructions, servings, prepTime, cookTime, totalTime, tags, notes: data.notes || [], }; // Validate against the schema return recipeSchema.parse(recipe); } catch (error) { console.error("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: any): 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; }