JSON and XML Data Binding in Winter Boot with PHP 8
Winter Boot provides first-class support for mapping JSON and XML payloads to strongly typed PHP objects using PHP 8 attributes. Annotate your model properties with #[JsonProperty] or the PAXB attributes (#[XmlElement], #[XmlAttribute], and friends), then let ObjectCreator or your REST controller deserialize the incoming data automatically. No boilerplate mapping code is required — the framework inspects attribute metadata at runtime and populates each field, including nested objects and typed lists. The same DTO class can carry both JSON and XML annotations, so a single object handles either format depending on the request’s Content-Type header.
JSON Serialization with #[JsonProperty]
Section titled “JSON Serialization with #[JsonProperty]”Apply #[JsonProperty] to any property you want to bind from a JSON payload. The framework maps the external JSON key to the PHP property and performs type coercion automatically.
use dev\winterframework\stereotype\JsonProperty;
class Product {
#[JsonProperty] // uses the property name "name" by default private string $name;
#[JsonProperty(name: 'unit_price')] // explicit external key private float $price;
#[JsonProperty(required: true)] private string $sku;
#[JsonProperty(name: 'tags', listClass: Tag::class)] private array $tags = []; // list of nested objects}#[JsonProperty] Options
Section titled “#[JsonProperty] Options”-
namestring | array(default:property name) The external JSON key name(s) to bind. Accepts a single string or an array of aliases — the first matching alias in the payload wins. -
requiredbool(default:false) Whentrue, anInvalidSyntaxExceptionis thrown if the key is absent from the payload. Automatically set totruewhen the property has no default value and its type does not allownull. -
nillablebool(default:false) Allowsnullvalues to be set on the property. Automatically set totruefor nullable types (?string, etc.). -
listClassstring(default:"") Fully qualified class name for the items when the property is a typed array. Each array element in the JSON payload is deserialized into the given class. -
validatearray(default:[]) An array of validator names or[validatorName, ...options]tuples applied to the property value after binding.
Explicit Deserialization with ObjectCreator
Section titled “Explicit Deserialization with ObjectCreator”Use ObjectCreator::createObject() to deserialize an associative array (e.g. the result of json_decode(..., true)) into a typed object:
use dev\winterframework\reflection\ObjectCreator;
$data = json_decode('{"name": "Pen", "unit_price": 2.01, "sku": "PEN-001"}', true);
$product = ObjectCreator::createObject(Product::class, $data);
echo $product->getName(); // "Pen"Automatic Binding in REST Controllers
Section titled “Automatic Binding in REST Controllers”When a controller method parameter is annotated with #[RequestBody] and the request carries Content-Type: application/json, the framework calls ObjectCreator::createObject() for you:
use dev\winterframework\web\rest\RestController;use dev\winterframework\web\RequestMapping;use dev\winterframework\web\RequestMethod;use dev\winterframework\web\MediaType;use dev\winterframework\web\RequestBody;use dev\winterframework\web\http\ResponseEntity;
#[RestController]class ProductController {
#[RequestMapping( path: '/api/v1/products', method: [RequestMethod::POST], consumes: [MediaType::APPLICATION_JSON] )] public function createProduct( #[RequestBody] Product $product ): ResponseEntity { // $product is fully populated from the JSON request body return ResponseEntity::ok()->withJson(['id' => 42]); }}XML Serialization with PAXB Attributes
Section titled “XML Serialization with PAXB Attributes”Winter Boot’s PAXB (PHP Architecture for XML Binding) mirrors Java’s JAXB, providing a set of attributes that describe how a class maps to an XML document. Annotate your class with #[XmlRootElement] and its properties with the appropriate XML attributes.
use dev\winterframework\paxb\attr\XmlRootElement;use dev\winterframework\paxb\attr\XmlElement;use dev\winterframework\paxb\attr\XmlAttribute;
#[XmlRootElement(name: 'product')]class Product {
#[XmlAttribute(name: 'sku')] private string $sku;
#[XmlElement(name: 'name')] private string $name;
#[XmlElement(name: 'unit_price')] private float $price;}The class above maps to the following XML document:
<product sku="PEN-001"> <name>Pen</name> <unit_price>2.01</unit_price></product>XML Attribute Reference
Section titled “XML Attribute Reference”namestring(default:class short name) The XML root element tag name. Place this attribute on the class itself.
namestring(default:property name) The XML child element tag name for this property.requiredbool(default:false) Throw a parse error if the element is missing from the document.nillablebool(default:false) Allow the element to be absent or carry anilattribute.namespacestring(default:"") XML namespace URI for this element.listClassstring(default:"") Fully qualified class name for repeated child elements of this type.
namestring(default:property name) The XML attribute name. Supported property types:int,bool,string,float,DateTime,DateTimeInterface.requiredbool(default:false) Throw a parse error if the attribute is absent from the element.namespacestring(default:"") XML namespace URI for this attribute.
Binds the text content of the enclosing element to the annotated property. Only one #[XmlValue] is allowed per class.
Additional XML Attributes
Section titled “Additional XML Attributes”#[XmlAnyElement]
Applied to a property, #[XmlAnyElement] captures any child elements not matched by other bindings. When lax is true, unknown elements are silently skipped rather than raising an error.
#[XmlAnyAttribute]
Applied to a property, #[XmlAnyAttribute] captures any XML attributes not matched by other bindings into an array on the property.
#[XmlPropertyOrder]
Applied to a class, #[XmlPropertyOrder] controls the serialisation order of child elements. The order parameter lists property names in the desired output order. Set ignoreUnknown to true to silently drop unrecognised elements during deserialization.
Explicit XML Deserialization
Section titled “Explicit XML Deserialization”Use ObjectCreator::createObjectXml() to parse an XML string directly into a typed object:
use dev\winterframework\reflection\ObjectCreator;
$xml = <<<XML<product sku="PEN-001"> <name>Pen</name> <unit_price>2.01</unit_price></product>XML;
$product = ObjectCreator::createObjectXml(Product::class, $xml);When you need finer control over parser properties, use XmlObjectMapper directly:
use dev\winterframework\paxb\XmlObjectMapper;
$mapper = new XmlObjectMapper();$mapper->setParserProperty(XMLReader::VALIDATE);$product = $mapper->readValue($xml, Product::class, validate: true);
// Serialise back to an XML string$xmlString = $mapper->writeValue($product);Complete Example — JSON + XML Model
Section titled “Complete Example — JSON + XML Model”The same model class can carry both JSON and XML annotations, letting a single DTO deserialize from either format based on the incoming Content-Type.
use dev\winterframework\stereotype\JsonProperty;use dev\winterframework\paxb\attr\XmlRootElement;use dev\winterframework\paxb\attr\XmlElement;use dev\winterframework\paxb\attr\XmlAttribute;
#[XmlRootElement(name: 'product')]class Product {
#[JsonProperty(name: 'sku')] #[XmlAttribute(name: 'sku')] private string $sku = '';
#[JsonProperty(name: 'name')] #[XmlElement(name: 'name')] private string $name = '';
#[JsonProperty(name: 'unit_price')] #[XmlElement(name: 'unit_price')] private float $price = 0.0;
#[JsonProperty(name: 'tags', listClass: Tag::class)] #[XmlElement(name: 'tag', listClass: Tag::class)] private array $tags = [];
public function getName(): string { return $this->name; } public function getPrice(): float { return $this->price; }}Declare both content types in your controller’s consumes list and the framework handles the rest:
#[RestController]class ProductController {
#[RequestMapping( path: '/api/v1/products', method: [RequestMethod::POST], consumes: [MediaType::APPLICATION_JSON, MediaType::APPLICATION_XML] )] public function createProduct( #[RequestBody] Product $product ): ResponseEntity { return ResponseEntity::created()->withJson([ 'name' => $product->getName(), 'price' => $product->getPrice(), ]); }}