Install the Package
composer require ambelz/json-to-form
Add to config/bundles.php:
Ambelz\JsonToFormBundle\JsonFormBundle::class => ['all' => true],
Create a JSON Form Definition
Place a JSON file (e.g., config/forms/my-form.json) with the required structure:
{
"slug": "my-form",
"sections": [
{
"slug": "user-details",
"title": "User Details",
"categories": [
{
"slug": "personal",
"title": "Personal Info",
"questions": [
{
"key": "name",
"type": "text",
"label": "Full Name",
"required": true
}
]
}
]
}
]
}
Render the Form in a Controller
use Ambelz\JsonToFormBundle\Service\JsonToFormTransformer;
#[Route('/form', name: 'form')]
public function showForm(JsonToFormTransformer $transformer): Response
{
$json = json_decode(file_get_contents('config/forms/my-form.json'), true);
$form = $transformer->transform($json, [], $this->createFormBuilder());
return $this->render('form.html.twig', ['form' => $form->getForm()]);
}
Display the Form in Twig
{{ form_start(form) }}
{{ form_widget(form) }}
<button type="submit">Submit</button>
{{ form_end(form) }}
Dynamic Form Generation
Use the JsonToFormTransformer service to convert JSON into a Symfony FormBuilder:
$transformer = $container->get(JsonToFormTransformer::class);
$form = $transformer->transform($jsonStructure, $initialData, $formBuilder);
Live Component Integration
Extend ComponentWithFormTrait for real-time updates:
#[AsLiveComponent('DynamicForm')]
class DynamicFormComponent extends AbstractController
{
use ComponentWithFormTrait;
#[LiveProp]
public array $jsonStructure;
protected function instantiateForm(): FormInterface
{
return $this->jsonToFormTransformer->transform($this->jsonStructure, [], $this->createFormBuilder());
}
#[LiveAction]
public function submit()
{
$this->submitForm();
if ($this->getForm()->isValid()) {
$this->addFlash('success', 'Submitted!');
}
}
}
Data Processing
Access submitted data via $form->getData():
$data = $form->getData(); // Returns nested array: ['sectionSlug']['categorySlug']['key'] => value
"constraints": ["NotBlank", "Length:max=100"]).JsonToFormTransformer to support non-standard field types (e.g., custom dropdowns).{
"label": "app.form.name.label",
"error_messages": {
"NotBlank": "app.form.name.error.not_blank"
}
}
JSON Schema Validation
The bundle does not validate the JSON structure against STRUCTURE.md. Invalid JSON (e.g., missing sections) will throw runtime errors. Use a validator or lint tool pre-deployment.
Form Data Nesting
Submitted data is deeply nested (sectionSlug > categorySlug > key). Flatten it before saving:
$flatData = array_reduce($data, 'array_merge_recursive', []);
Live Component Pitfalls
$jsonStructure after the component mounts; it resets the form.form_start() includes CSRF protection in non-LiveComponent forms.Caching
The transformer does not cache parsed forms. For large forms, cache the FormInterface instance:
$form = $cache->get('form_my-form', function() use ($transformer, $json) {
return $transformer->transform($json, [], $this->createFormBuilder());
});
config/packages/dev/form.yaml:
framework:
form:
enabled_attributes: true
try {
$form = $transformer->transform($json, [], $builder);
} catch (\InvalidArgumentException $e) {
\Log::error("Invalid JSON: " . $e->getMessage());
}
Custom Field Types
Override the transformer’s getFieldType() method:
class CustomJsonToFormTransformer extends JsonToFormTransformer
{
protected function getFieldType(array $question): string
{
return $question['type'] === 'custom' ? 'text' : parent::getFieldType($question);
}
}
Post-Transform Hooks
Use Symfony’s FormEvent listeners to modify forms after transformation:
$form->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $event) {
$event->getForm()->add('custom_field', TextType::class);
});
Dynamic JSON Loading Fetch JSON from an API or database:
$json = json_decode($apiClient->request('GET', '/forms/my-form')->getContent(), true);
How can I help you explore Laravel packages today?