Source code for fabricpy.recipejson
# fabricpy/recipejson.py
"""Recipe JSON handling for Fabric mods.
This module provides the RecipeJson class for defining and managing
Minecraft recipe data. Recipes define how items and blocks can be crafted,
smelted, or otherwise created through various game mechanics.
The RecipeJson class handles both string and dictionary representations
of recipe data, with validation and convenient access to recipe properties.
"""
from __future__ import annotations
import json
from typing import Any
[docs]
class RecipeJson:
"""Wrapper for Minecraft recipe JSON data.
This class holds a validated dictionary representation of a Minecraft recipe
along with the original JSON text. It provides validation, convenient property
access, and ensures the exact text can be written back to disk unchanged.
Recipes define how items are crafted, smelted, or created through other
game mechanics. They must follow Minecraft's recipe JSON format.
Args:
src (str | dict[str, Any]): Recipe data as either a JSON string or
a dictionary. If a string, it will be parsed as JSON. If a dict,
it will be used directly and converted to JSON text.
Attributes:
text (str): The JSON string representation of the recipe.
data (dict[str, Any]): The parsed dictionary representation of the recipe.
Raises:
ValueError: If the recipe is missing a required 'type' field or has
an invalid 'type' value.
json.JSONDecodeError: If the input string is not valid JSON.
Example:
Creating a recipe from JSON string::
recipe_json = '''
{
"type": "minecraft:crafting_shaped",
"pattern": ["##", "##"],
"key": {"#": "minecraft:stone"},
"result": {"id": "mymod:stone_block", "count": 1}
}
'''
recipe = RecipeJson(recipe_json)
Creating a recipe from dictionary::
recipe_data = {
"type": "minecraft:smelting",
"ingredient": "minecraft:iron_ore",
"result": "minecraft:iron_ingot",
"experience": 0.7,
"cookingtime": 200
}
recipe = RecipeJson(recipe_data)
Getting the result item ID::
result_id = recipe.result_id # "mymod:stone_block"
"""
[docs]
def __init__(self, src: str | dict[str, Any]):
"""Initialize a new RecipeJson instance.
Args:
src (str | dict[str, Any]): Recipe data as JSON string or dictionary.
Raises:
ValueError: If recipe is missing 'type' field or has invalid 'type'.
json.JSONDecodeError: If input string is not valid JSON.
"""
if isinstance(src, str):
self.text: str = src.strip()
self.data: dict[str, Any] = json.loads(self.text)
else: # already a dict
self.data = src
self.text = json.dumps(src, indent=2)
# minimal sanity-check – make sure the mandatory "type" key exists and is a non-empty string
if "type" not in self.data:
raise ValueError("Recipe JSON must contain a 'type' field")
recipe_type = self.data["type"]
if not isinstance(recipe_type, str) or not recipe_type.strip():
raise ValueError("Recipe 'type' field must be a non-empty string")
# convenience helpers ------------------------------------------------
@property
def result_id(self) -> str | None:
"""Get the result item identifier from the recipe.
Extracts the item ID from the recipe's result field, which is used
to name the generated recipe JSON file. Handles both string and
dictionary result formats.
Returns:
str | None: The result item identifier (e.g., "mymod:stone_block"),
or None if no valid result ID is found.
Example:
Getting result ID from different recipe formats::
# String result format
recipe1 = RecipeJson({"type": "minecraft:smelting", "result": "minecraft:iron_ingot"})
print(recipe1.result_id) # "minecraft:iron_ingot"
# Dictionary result format (1.21+)
recipe2 = RecipeJson({
"type": "minecraft:crafting_shaped",
"result": {"id": "mymod:custom_item", "count": 2}
})
print(recipe2.result_id) # "mymod:custom_item"
# Dictionary result format (pre-1.21)
recipe3 = RecipeJson({
"type": "minecraft:crafting_shaped",
"result": {"item": "mymod:legacy_item", "count": 1}
})
print(recipe3.result_id) # "mymod:legacy_item"
"""
res = self.data.get("result")
if res is None:
return None
# Handle string results
if isinstance(res, str):
return res
# Handle dict results
if isinstance(res, dict):
# 1.21+: "id"; pre-1.21 uses "item" – support either
# Only return string values
result_id = res.get("id")
if isinstance(result_id, str):
return result_id
result_item = res.get("item")
if isinstance(result_item, str):
return result_item
return None
# Handle other types (numbers, booleans, etc.) - return None
return None
[docs]
def get_result_id(self) -> str | None:
"""Get the result ID from the recipe.
This is an alias for the result_id property, provided for backward
compatibility and explicit method-style access.
Returns:
str | None: The result item identifier, or None if not found.
Example:
Using the method-style accessor::
recipe = RecipeJson({"type": "minecraft:smelting", "result": "minecraft:iron_ingot"})
result = recipe.get_result_id() # "minecraft:iron_ingot"
"""
return self.result_id