{"id":13593086,"url":"https://github.com/Crell/Serde","last_synced_at":"2025-04-09T02:32:18.799Z","repository":{"id":40552697,"uuid":"394407482","full_name":"Crell/Serde","owner":"Crell","description":"Robust Serde (serialization/deserialization) library for PHP 8.","archived":false,"fork":false,"pushed_at":"2024-12-07T05:51:26.000Z","size":616,"stargazers_count":312,"open_issues_count":13,"forks_count":15,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-04-06T19:05:15.520Z","etag":null,"topics":["deserialization","json","php","serialization","xml"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Crell.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["Crell"]}},"created_at":"2021-08-09T19:01:16.000Z","updated_at":"2025-03-09T13:05:12.000Z","dependencies_parsed_at":"2023-10-16T06:26:13.294Z","dependency_job_id":"ad96949a-4d9f-4c3c-a187-2dc6b127648c","html_url":"https://github.com/Crell/Serde","commit_stats":{"total_commits":486,"total_committers":9,"mean_commits":54.0,"dds":"0.053497942386831254","last_synced_commit":"10f60669030d1c1846ed39e4130d46c91f8b217f"},"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Crell%2FSerde","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Crell%2FSerde/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Crell%2FSerde/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Crell%2FSerde/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Crell","download_url":"https://codeload.github.com/Crell/Serde/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247965650,"owners_count":21025412,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["deserialization","json","php","serialization","xml"],"created_at":"2024-08-01T16:01:16.440Z","updated_at":"2025-04-09T02:32:18.154Z","avatar_url":"https://github.com/Crell.png","language":"PHP","funding_links":["https://github.com/sponsors/Crell"],"categories":["PHP"],"sub_categories":[],"readme":"# Serde\n\n[![Latest Version on Packagist][ico-version]][link-packagist]\n[![Software License][ico-license]](LICENSE.md)\n[![Total Downloads][ico-downloads]][link-downloads]\n\nSerde (pronounced \"seer-dee\") is a fast, flexible, powerful, and easy to use serialization and deserialization library for PHP that supports a number of standard formats.  It draws inspiration from both Rust's Serde crate and Symfony Serializer, although it is not directly based on either.\n\nAt this time, Serde supports serializing PHP objects to and from PHP arrays, JSON, YAML, TOML, and CSV files.  It also supports serializing to JSON or CSV via a stream.  Further support is planned, but by design can also be extended by anyone.\n\n## Install\n\nVia Composer\n\n``` bash\n$ composer require crell/serde\n```\n\n## Usage\n\nSerde is designed to be both quick to start using and robust in more advanced cases.  In its most basic form, you can do the following:\n\n```php\nuse Crell\\Serde\\SerdeCommon;\n\n$serde = new SerdeCommon();\n\n$object = new SomeClass();\n// Populate $object somehow;\n\n$jsonString = $serde-\u003eserialize($object, format: 'json');\n\n$deserializedObject = $serde-\u003edeserialize($jsonString, from: 'json', to: SomeClass::class);\n```\n\n(The named arguments are optional, but recommended.)\n\nSerde is highly configurable, but common cases are supported by just using the `SerdeCommon` class as provided.  For most basic cases, that is all you need.\n\n## Key features\n\n### Supported formats\n\nSerde can serialize to:\n\n* PHP arrays (`array`)\n* JSON (`json`)\n* Streaming JSON (`json-stream`)\n* YAML (`yaml`)\n* TOML (`toml`)\n* CSV (`csv`)\n* Streaming CSV (`csv-stream`)\n\nSerde can deserialize from:\n\n* PHP arrays (`array`)\n* JSON (`json`)\n* YAML (`yaml`)\n* TOML (`toml`)\n* CSV (`csv`)\n\nYAML support requires the [`Symfony/Yaml`](https://github.com/symfony/yaml) library.\n\nTOML support requires the [`Vanodevium/Toml`](https://github.com/vanodevium/toml) library.\n\nXML support is in progress.\n\n### Robust object support\n\nSerde automatically supports nested objects in properties of other objects, which will be handled recursively as long as there are no circular references.\n\nSerde handles `public`, `private`, `protected`, and `readonly` properties, both reading and writing, with optional default values.\n\nIf you try to serialize or deserialize an object that implements PHP's [`__serialize()`](https://www.php.net/manual/en/language.oop5.magic.php#object.serialize) or [`__unserialize()`](https://www.php.net/manual/en/language.oop5.magic.php#object.unserialize) hooks, those will be respected.  (If you want to read/write from PHP's internal serialization format, just call `serialize()`/`unserialize()` directly.)\n\nSerde also supports post-load callbacks that allow you to re-initialize derived information if necessary without storing it in the serialized format.\n\nPHP objects can be mutated to and from a serialized format.  Nested objects can be flattened or collected, classes with common interfaces can be mapped to the appropriate object, and array values can be imploded into a string for serialization and exploded back into an array when reading.\n\n## Configuration\n\nSerde's behavior is driven almost entirely through attributes.  Any class may be serialized from or deserialized to as-is with no additional configuration, but there is a great deal of configuration that may be opted-in to.\n\nAttribute handling is provided by [`Crell/AttributeUtils`](https://github.com/Crell/AttributeUtils).  It is worth looking into as well.\n\nThe main attribute is the `Crell\\Serde\\Attributes\\Field` attribute, which may be placed on any object property.  (Static properties are ignored.)  All of its arguments are optional, as is the `Field` itself.  (That is, adding `#[Field]` with no arguments is the same as not specifying it at all.)  The meaning of the available arguments is listed below.\n\nAlthough not required, it is strongly recommended that you always use named arguments with attributes.  The precise order of arguments is *not guaranteed*.\n\nIn the examples below, the `Field` is generally referenced directly.  However, you may also import the namespace and then use namespaced versions of the attributes, like so:\n\n```php\nuse Crell\\Serde\\Attributes as Serde;\n\n#[Serde\\ClassSettings(includeFieldsByDefault: false)]\nclass Person\n{\n    #[Serde\\Field(serializedName: 'callme')]\n    protected string $name = 'Larry';\n}\n```\n\nWhich you do is mostly a matter of preference, although if you are mixing Serde attributes with attributes from other libraries then the namespaced approach is advisable.\n\nThere is also a `ClassSettings` attribute that may be placed on classes to be serialized.  At this time it has four arguments:\n\n* `includeFieldsByDefault`, which defaults to `true`.  If set to false, a property with no `#[Field]` attribute will be ignored.  It is equivalent to setting `exclude: true` on all properties implicitly.\n* `requireValues`, which defaults to `false`.  If set to true, then when deserializing any field that is not provided in the incoming data will result in an exception.  This may also be turned on or off on a per-field level.  (See `requireValue` below.)  The class-level setting applies to any field that does not specify its behavior.\n* `renameWith`.  If set, the specified renaming strategy will be used for all properties of the class, unless a property specifies its own.  (See `renameWith` below.)  The class-level setting applies to any field that does not specify its behavior.\n* `omitNullFields`, which defaults to false.  If set to true, any property on the class that is null will be omitted when serializing. It has on effect on deserialization.  This may also be turned on or off on a per-field level.  (See `omitIfNull` below.)\n* `scopes`, which sets the scope of a given class definition attribute.  See the section on Scopes below.\n\n### `exclude` (bool, default false)\n\nIf set to `true`, Serde will ignore the property entirely on both serializing and deserializing.\n\n### `serializedName` (string, default null)\n\nIf provided, this string will be used as the name of a property when serialized out to a format and when reading it back in.  for example:\n\n```php\nuse Crell\\Serde\\Attributes\\Field;\n\nclass Person\n{\n    #[Field(serializedName: 'callme')]\n    protected string $name = 'Larry';\n}\n```\n\nRound trips to/from:\n\n```json\n{\n    \"callme\": \"Larry\"\n}\n```\n\n### `renameWith` (RenamingStrategy, default null)\n\nThe `renameWith` key specifies a way to mangle the name of the property to produce a serializedName.  The most common examples here would be case folding, say if serializing to a format that uses a different convention than PHP does.\n\nThe value of `renameWith` can be any object that implements the [`RenamingStrategy`](src/Renaming/RenamingStrategy.php) interface.  The most common versions are already provided via the `Cases` enum and `Prefix` class, but you are free to provide your own.\n\nThe `Cases` enum implements `RenamingStrategy` and provides a series of instances (cases) for common renaming.  For example:\n\n```php\nuse Crell\\Serde\\Attributes\\Field;\nuse Crell\\Serde\\Renaming\\Cases;\n\nclass Person\n{\n    #[Field(renameWith: Cases::snake_case)]\n    public string $firstName = 'Larry';\n\n    #[Field(renameWith: Cases::CamelCase)]\n    public string $lastName = 'Garfield';\n}\n```\n\nSerializes to/from:\n\n```json\n{\n    \"first_name\": \"Larry\",\n    \"LastName\": \"Garfield\"\n}\n```\n\nAvailable cases are:\n\n* `Cases::UPPERCASE`\n* `Cases::lowercase`\n* `Cases::snake_case`\n* `Cases::kebab_case` (renders with dashes, not underscores)\n* `Cases::CamelCase`\n* `Cases::lowerCamelCase`\n\nThe `Prefix` class attaches a prefix to values when serialized, but otherwise leaves the property name intact.\n\n```php\nuse Crell\\Serde\\Attributes\\Field;\nuse Crell\\Serde\\Renaming\\Prefix;\n\nclass MailConfig\n{\n    #[Field(renameWith: new Prefix('mail_')]\n    protected string $host = 'smtp.example.com';\n\n    #[Field(renameWith: new Prefix('mail_')]\n    protected int $port = 25;\n\n    #[Field(renameWith: new Prefix('mail_')]\n    protected string $user = 'me';\n\n    #[Field(renameWith: new Prefix('mail_')]\n    protected string $password = 'sssh';\n}\n```\n\nSerializes to/from:\n\n```json\n{\n    \"mail_host\": \"smtp.example.com\",\n    \"mail_port\": 25,\n    \"mail_user\": \"me\",\n    \"mail_password\": \"sssh\"\n}\n```\n\nIf both `serializedName` and `renameWith` are specified, `serializedName` will be used and `renameWith` ignored.\n\n### `alias` (array, default `[]`)\n\nWhen deserializing (only), if the expected serialized name is not found in the incoming data, these additional property names will be examined to see if the value can be found.  If so, the value will be read from that key in the incoming data.  If not, it will behave the same as if the value was simply not found in the first place.\n\n```php\nuse Crell\\Serde\\Attributes\\Field;\n\nclass Person\n{\n    #[Field(alias: ['layout', 'design'])]\n    protected string $format = '';\n}\n```\n\nAll three of the following JSON strings would be read into an identical object:\n\n```json\n{\n    \"format\": \"3-column-layout\"\n}\n```\n\n```json\n{\n    \"layout\": \"3-column-layout\"\n}\n```\n\n```json\n{\n    \"design\": \"3-column-layout\"\n}\n```\n\nThis is mainly useful when an API key has changed, and legacy incoming data may still have an old key name.\n\n### `omitIfNull` (bool, default false)\n\nThis key only applies on serialization.  If set to true, and the value of this property is null when an object is serialized, it will be omitted from the output entirely.  If false, a `null` will be written to the output, however that looks for the particular format.\n\n### `useDefault` (bool, default true)\n\nThis key only applies on deserialization.  If a property of a class is not found in the incoming data, and this property is true, then a default value will be assigned instead.  If false, the value will be skipped entirely.  Whether the deserialized object is now in an invalid state depends on the object.\n\nThe default value to use is derived from a number of different locations.  The priority order of defaults is:\n\n1. The value provided by the `default` argument to the `Field` attribute.\n2. The default value provided by the code, as reported by Reflection.\n3. The default value of an identically named constructor argument, if any.\n\nSo for example, the following class:\n\n```php\nuse Crell\\Serde\\Attributes\\Field;\n\nclass Person\n{\n    #[Field(default: 'Hidden')]\n    public string $location;\n\n    #[Field(useDefault: false)]\n    public int $age;\n\n    public function __construct(\n        public string $name = 'Anonymous',\n    ) {}\n}\n```\n\nif deserialized from an empty source (such as `{}` in JSON), will result in an object with `location` set to `Hidden`, `name` set to `Anonymous`, and `age` still uninitialized.\n\n### `default` (mixed, default null)\n\nThis key only applies on deserialization.  If specified, then if a value is missing in the incoming data being deserialized this value will be used instead, regardless of what the default in the source code itself is.\n\n### `strict` (bool, default true)\n\nThis key only applies on deserialization.  If set to `true`, a type mismatch in the incoming data will be rejected and an exception thrown.  If `false`, a deformatter will attempt to cast an incoming value according to PHP's normal casting rules.  That means, for example, `\"1\"` is a valid value for an integer property if `strict` is `false`, but will throw an exception if set to `true`.\n\nFor sequence fields, `strict` set to `true` will reject a non-sequence value.  (It must pass an `array_is_list()` check.)  If `strict` is `false`, any array-ish value will be accepted but passed through `array_values()` to discard any keys and reindex it.\n\nAdditionally, in non-`strict` mode, numeric strings in the incoming array will be cast to ints or floats as appropriate in both sequence fields and dictionary fields.  In `strict` mode, numeric strings will still be rejected.\n\nThe exact handling of this setting may vary slightly depending on the incoming format, as some formats handle their own types differently.  (For instance, everything is a string in XML.)\n\n### `requireValue` (bool, default false)\n\nThis key only applies on deserialization.  If set to `true`, if the incoming data does not include a value for this field and there is no default specified, a `MissingRequiredValueWhenDeserializing` exception will be thrown.  If not set, and there is no default value, then the property will be left uninitialized.\n\nIf a field has a default value, then the default value will always be used for missing data and this setting has no effect.\n\n### `flatten` (bool, default false)\n\nThe `flatten` keyword can only be applied on an array or object property.  A property that is \"flattened\" will have all of its properties injected into the parent directly on serialization, and will have values from the parent \"collected\" into it on deserialization.\n\nMultiple objects and arrays may be flattened (serialized), but on deserialization only the lexically last array property marked `flatten` will collect remaining keys.  Any number of objects may \"collect\" their properties, however.\n\nAs an example, consider pagination.  It may be very helpful to represent pagination information in PHP as an object property of a result set, but in the serialized JSON or XML you may want the extra object removed.\n\nGiven this set of classes:\n\n```php\nuse Crell\\Serde\\Attributes as Serde;\n\nclass Results\n{\n    public function __construct(\n        #[Serde\\Field(flatten: true)]\n        public Pagination $pagination,\n        #[Serde\\SequenceField(arrayType: Product::class)]\n        public array $products,\n    ) {}\n}\n\nclass Pagination\n{\n    public function __construct(\n        public int $total,\n        public int $offset,\n        public int $limit,\n    ) {}\n}\n\nclass Product\n{\n    public function __construct(\n        public string $name,\n        public float $price,\n    ) {}\n}\n```\n\nWhen serialized, the `$pagination` object will get \"flattened,\" meaning its three properties will be included directly in the properties of `Results`.  Therefore, a JSON-serialized copy of this object may look like:\n\n```json\n{\n    \"total\": 100,\n    \"offset\": 20,\n    \"limit\": 10,\n    \"products\": [\n        {\n            \"name\": \"Widget\",\n            \"price\": 9.99\n        },\n        {\n            \"name\": \"Gadget\",\n            \"price\": 4.99\n        }\n    ]\n}\n```\n\nThe extra \"layer\" of the `Pagination` object has been removed.  When deserializing, those extra properties will be \"collected\" back into a `Pagination` object.\n\nNow consider this more complex example:\n\n```php\nuse Crell\\Serde\\Attributes as Serde;\n\nclass DetailedResults\n{\n    public function __construct(\n        #[Serde\\Field(flatten: true)]\n        public NestedPagination $pagination,\n        #[Serde\\Field(flatten: true)]\n        public ProductType $type,\n        #[Serde\\SequenceField(arrayType: Product::class)]\n        public array $products,\n        #[Serde\\Field(flatten: true)]\n        public array $other = [],\n    ) {}\n}\n\nclass NestedPagination\n{\n    public function __construct(\n        public int $total,\n        public int $limit,\n        #[Serde\\Field(flatten: true)]\n        public PaginationState $state,\n    ) {}\n}\n\nclass PaginationState\n{\n    public function __construct(\n        public int $offset,\n    ) {\n    }\n}\n\nclass ProductType\n{\n    public function __construct(\n        public string $name = '',\n        public string $category = '',\n    ) {}\n}\n```\n\nIn this example, both `NestedPagination` and `PaginationState` will be flattened when serializing.  `NestedPagination` itself also has a field that should be flattened.  Both will flatten and collect cleanly, as long as none of them share a property name.\n\nAdditionally, there is an extra array property, `$other`. `$other` may contain whatever associative array is desired, and its values will also get flattened into the output.\n\nWhen collecting, only the lexically last flattened array will get any data, and will get all properties not already accounted for by some other property.  For example, an instance of `DetailedResults` may serialize to JSON as:\n\n```json\n{\n    \"total\": 100,\n    \"offset\": 20,\n    \"limit\": 10,\n    \"products\": [\n        {\n            \"name\": \"Widget\",\n            \"price\": 9.99\n        },\n        {\n            \"name\": \"Gadget\",\n            \"price\": 4.99\n        }\n    ],\n    \"foo\": \"beep\",\n    \"bar\": \"boop\"\n}\n```\n\nIn this case, the `$other` property has two keys, `foo` and `bar`, with values `beep` and `boop`, respectively.  The same JSON will deserialize back to the same object as before.\n\n#### Value objects\n\nFlattening can also be used in conjunction with renaming to silently translate value objects.  Consider:\n\n```php\nclass Person\n{\n    public function __construct(\n        public string $name,\n        #[Field(flatten: true)]\n        public Age $age,\n        #[Field(flatten: true)]\n        public Email $email,\n    ) {}\n}\n\nreadonly class Email\n{\n    public function __construct(\n        #[Field(serializedName: 'email')] public string $value,\n    ) {}\n}\n\nreadonly class Age\n{\n    public function __construct(\n        #[Field(serializedName: 'age')] public int $value\n    ) {\n        $this-\u003evalidate();\n    }\n\n    #[PostLoad]\n    private function validate(): void\n    {\n        if ($this-\u003evalue \u003c 0) {\n            throw new \\InvalidArgumentException('Age cannot be negative.');\n        }\n    }\n}\n```\n\nIn this example, `Email` and `Age` are value objects, in the latter case with extra validation.  However, both are marked `flatten: true`, so their properties will be moved up a level to `Person` when serializing.  However, they both use the same property name, so both have a custom serialization name specified.  The above object will serialize to (and deserialize from) something like this:\n\n```json\n{\n    \"name\": \"Larry\",\n    \"age\": 21,\n    \"email\": \"me@example.com\"\n}\n```\n\nNote that because deserialization bypasses the constructor, the extra validation in `Age` must be placed in a separate method that is called from the constructor and flagged to run automatically after deserialization.\n\nIt is also possible to specify a prefix for a flattened value, which will also be applied recursively.  For example, assuming the same Age class above:\n\n```php\nreadonly class JobDescription\n{\n    public function __construct(\n        #[Field(flatten: true, flattenPrefix: 'min_')]\n        public Age $minAge,\n        #[Field(flatten: true, flattenPrefix: 'max_')]\n        public Age $maxAge,\n    ) {}\n}\n\nclass JobEntry\n{\n    public function __construct(\n        #[Field(flatten: true, flattenPrefix: 'desc_')]\n        public JobDescription $description,\n    ) {}\n}\n```\n\nIn this case, serializing `JobEntry` will first flatten the `$description` property, with `desc_` as a prefix.  Then, `JobDescription` will flatten both of its age fields, giving each a separate prefix.  That will result in a serialized output something like this:\n\n```json\n{\n    \"desc_min_age\": 18,\n    \"desc_max_age\": 65,\n}\n```\n\nAnd it will deserialize back to the same original 3-layer-object structure.\n\n### `flattenPrefix` (string, default '')\n\nWhen an object or array property is flattened, by default its properties will be flattened using their existing name (or `serializedName`, if specified).  That may cause issues if the same class is included in a parent class twice, or if there is some other name collission.  Instead, flattened fields may be given a `flattenPrefix` value.  That string will be prepended to the name of the property when serializing.\n\nIf set on a non-flattened field, this value is meaningless and has no effect.\n\n### Sequences and Dictionaries\n\nIn most languages, and many serialization formats, there is a difference between a sequential list of values (called variously an array, sequence, or list) and a map of arbitrary size of arbitrary values to other arbitrary values (called a dictionary or map).  PHP does not make a distinction, and shoves both data types into a single associative array variable type.\n\nSometimes that works out, but other times the distinction between the two greatly matters.  To support those cases, Serde allows you to flag an array property as either a `#[SequenceField]` or `#[DictionaryField]` (and it is recommended that you always do so).  Doing so ensures that the correct serialization pathway is used for the property, and also opens up a number of additional features.\n\n#### `arrayType`\n\nOn both a `#[SequenceField]` and `#[DictionaryField]`, the `arrayType` argument lets you specify the type that all values in that structure are.  For example, a sequence of integers can easily be serialized to and deserialized from most formats without any additional help.  However, an ordered list of `Product` objects could be serialized, but there's no way to tell then how to deserialize that data back to `Product` objects rather than just a nested associative array (which would also be legal).  The `arrayType` argument solves that issue.\n\nIf `arrayType` is specified, then all values of that array are assumed to be of that type.  It may either be a `class-string` to specify all values are a class, or a value of the `ValueType` enum to indicate one of the four supported scalars.\n\nOn deserialization, then, Serde will either validate that all incoming values are of the right scalar type, or look for nested object-like structures (depending on the specific format), and convert those into the specified object type.\n\nFor example:\n\n```php\nuse Crell\\Serde\\Attributes\\SequenceField;\n\nclass Order\n{\n    public string $orderId;\n\n    public int $userId;\n\n    #[SequenceField(arrayType: Product::class)]\n    public array $products;\n}\n```\n\nIn this case, the attribute tells Serde that `$products` is an indexed, sequential list of `Product` objects.  When serializing, that may be represented as an array of dictionaries (in JSON or YAML) or perhaps with some additional metadata in other formats.\n\nWhen deserializing, the otherwise object-ignorant data will be upcast back to `Product` objects.\n\n`arrayType` works the exact same way on a `DictionaryField`.\n\n#### `keyType`\n\nOn `DictionaryField` only, it's possible to restrict the array to only allowing integer or string keys.  It has two legal values, `KeyType::Int` and `KeyType::String` (an enum).  If set to `KeyType::Int`, then deserialization will reject any arrays that have string keys, but will accept numeric strings.  If set to `KeyType::String`, then deserialization will reject any arrays that have integer keys, including numeric strings.\n\n(PHP auto-casts integer string array keys to actual integers, so there is no way to allow them in string-based dictionaries.)\n\nIf no value is set, then either key type will be accepted.\n\n#### `implodeOn`\n\nThe `implodeOn` argument to `SequenceField`, if present, indicates that the value should be joined into a string serialization, using the provided value as glue.  For example:\n\n```php\nuse Crell\\Serde\\Attributes\\SequenceField;\n\nclass Order\n{\n    #[SequenceField(implodeOn: ',')]\n    protected array $productIds = [5, 6, 7];\n}\n```\n\nWill serialize in JSON to:\n\n```json\n{\n    \"productIds\": \"5,6,7\"\n}\n```\n\nOn deserialization, that string will get automatically get exploded back into an array when placed into the object.\n\nBy default, on deserialization the individual values will be `trim()`ed to remove excess whitespace.  That can be disabled by setting the `trim` attribute argument to `false`.\n\n#### `joinOn`\n\n`DictionaryField`s also support imploding/exploding on serialization, but require two keys.  `implodeOn` specifies the string to use between distinct values.  `joinOn` specifies the string to use between the key and value.\n\nFor example:\n\n```php\nuse Crell\\Serde\\Attributes\\DictionaryField;\n\nclass Settings\n{\n    #[DictionaryField(implodeOn: ',', joinOn: '=')]\n    protected array $dimensions = [\n        'height' =\u003e 40,\n        'width' =\u003e 20,\n    ];\n}\n```\n\nWill serialize/deserialize to this JSON:\n\n```json\n{\n    \"dimensions\": \"height=40,width=20\"\n}\n```\n\nAs with `SequenceField`, values will automatically be `trim()`ed unless `trim: false` is specified in the attribute's argument list.\n\n### Date and Time fields\n\n`DateTime` and `DateTimeImmutable` fields can also be serialized, and you can control how they are serialized using the `DateField` or the `UnixTimeField` attribute.  `DateField` has two arguments, which may be used individually or together.  Specifying neither is the same as not specifying the `DateField` attribute at all.\n\n```php\nuse Crell\\Serde\\Attributes\\DateField;\n\nclass Settings\n{\n    #[DateField(format: 'Y-m-d')]\n    protected DateTimeImmutable $date = new DateTimeImmutable('4 July 2022-07-04 14:22);\n}\n```\n\nWill serialize to this JSON:\n\n```json\n{\n    \"date\": \"2022-07-04\"\n}\n```\n\n#### `timezone`\n\nThe `timezone` argument may be any timezone string legal in PHP, such as `America/Chicago` or `UTC`.  If specified, the value will be cast to this timezone first before it is serialized.  If not specified, the value will be left in whatever timezone it is in before being serialized.  Whether that makes a difference to the output depends on the `format`.\n\nOn deserializing, the `timezone` has no effect.  If the incoming value has a timezone specified, the resulting `DateTime[Immutable]` object will use that timezone.  If not, the system default timezone will be used.\n\n#### `format`\n\nThis argument lets you specify the format that will be used when serializing.  It may be any string accepted by PHP's [date_format syntax](https://www.php.net/manual/en/datetimeimmutable.createfromformat.php), including one of the various constants defined on `DateTimeInterface`.  If not specified, the default format is `RFC3339_EXTENDED`, or `Y-m-d\\TH:i:s.vP`.  While not the most human-friendly, it is the default format used by Javascript/JSON so makes for reasonable compatibility.\n\nOn deserializing, the `format` has no effect.  Serde will pass the string value to a `DateTime` or `DateTimeImmutable` constructor, so any format recognized by PHP will be parsed according to PHP's standard date-parsing rules.\n\n#### Unix Time\n\nIn cases where you need to serialize the date to/from Unix Time, you can use `UnixTimeField`,\nwhich supports a resolution parameter that can handle up to microsecond resolution:\n\n```php\nuse Crell\\Serde\\Attributes\\UnixTimeField;\nuse Crell\\Serde\\Attributes\\Enums\\UnixTimeResolution;\n\nclass Jwt\n{\n    #[UnixTimeField]\n    protected DateTimeImmutable $exp;\n    \n    #[UnixTimeField(resolution: UnixTimeResolution::Milliseconds)]\n    protected DateTimeImmutable $iss;\n}\n```\n\nWill serialize to this JSON:\n\n```json\n{\n    \"exp\": 1707764358,\n    \"iss\": 1707764358000\n}\n```\n\nThe serialized integer should be read as \"this many seconds since the epoc\" or \"this many milliseconds since the epoc,\" etc.  (\"The epoc\" being 1 January 1970, the first year after humans first walked on the moon.)\n\nNote that the permissible range of milliseconds and microseconds is considerably smaller than that for seconds, since there is a limit on the size of an integer that we can represent.  For timestamps in the early 21st century there should be no issue, but trying to record the microseconds since the epoc for the setting of Dune (somewhere in the 10,000s) won't work.\n\n### Generators, Iterables, and Traversables\n\nPHP has a number of \"lazy list\" options.  Generally, they are all objects that implement the `\\Traversable` interface.  However, there are several syntax options available with their own subtleties.  Serde supports them in different ways.\n\nIf a property is defined to be an `iterable`, then regardless of whether it's a `Traversable` object or a Generator the iterable will be \"run out\" and converted to an array by the serialization process.  Note that if the iterable is an infinite iterator, the process will continue forever and your program will freeze.  Don't do that.\n\nAlso, when using an `iterable` property the property MUST be marked with either `#[SequenceField]` or `#[DictionaryField]` as appropriate.  Serde cannot deduce which it is on its own the way it (usually) can with arrays.\n\nOn deserializing, the incoming values will always be assigned to an array.  As an array is an `iterable`, that is still type safe.  While in theory it would be possible to build a dynamic generator on the fly to materialize the values lazily, that would not actually save any memory.\n\nNote this does mean that serializing and deserializing an object will not be fully symmetric.  The initial object may have properties that are generators, but the deserialized object will have arrays instead.\n\nIf a property is typed to be some other `Traversable` object (usually because it implements either `\\Iterator` or `\\IteratorAggregate`), then it will be serialized and deserialized as a normal object.  Its `iterable`-ness is ignored.  In this case, the `#[SequenceField]` and `#[DictionaryField]` attributes are forbidden.\n\n### CSV Formatter\n\nSerde includes support for serializing/deserializing CSV files.  However, because CSV is a more limited type of format only certain object structures are supported.\n\nSpecifically, the object in question must have a single property that is marked `#[SequenceField]`, and it must have an explicit `arrayType` that is a class.  That class, in turn, may contain only `int`, `float`, or `string` properties.  Anything else will throw an error.\n\nFor example:\n\n```php\nnamespace Crell\\Serde\\Records;\n\nuse Crell\\Serde\\Attributes\\SequenceField;\n\nclass CsvTable\n{\n    public function __construct(\n        #[SequenceField(arrayType: CsvRow::class)]\n        public array $people,\n    ) {}\n}\n\nclass CsvRow\n{\n    public function __construct(\n        public string $name,\n        public int $age,\n        public float $balance,\n    ) {}\n}\n```\n\nThis combination will result in a three-column CSV file, and also deserialize from a three-column CSV file.\n\nThe CSV formatter uses PHP's native CSV parsing and writing tools.  If you want to control the delimiters used, pass those as constructor arguments to a `CsvFormatter` instance and inject that into the `Serde` class instead of the default.\n\nNote that the lone property may be a generator.  That allows a CSV to be generated on the fly off of arbitrary data.  When deserialized, it will still deserialize to an array.\n\n### Streams\n\nSerde includes two stream-based formatters (but not deformatters, yet), one for JSON and one for CSV.  They work nearly the same way as any other formatter, but when calling `$serde-\u003eserialize()` you may (and should) pass an extra `init` argument.  `$init` should be an instance of `Serde\\Formatter\\FormatterStream`, which wraps a writeable PHP stream handle.\n\nThe value returned will then be that same stream handle, after the object to be serialized has been written to it.\n\nFor example:\n\n```php\n// The JsonStreamFormatter and CsvStreamFormatter are not included by default.\n$s = new SerdeCommon(formatters: [new JsonStreamFormatter()]);\n\n// You may use any PHP supported stream here, including files, network sockets,\n// stdout, an in-memory temp stream, etc.\n$init = FormatterStream::new(fopen('/tmp/output.json', 'wb'));\n\n$result = $serde-\u003eserialize($data, format: 'json-stream', init: $init);\n\n// $result is a FormatterStream object that wraps the same handle as before.\n// What you can now do with the stream depends on what kind of stream it is.\n```\n\nIn this example, the `$data` object (whatever it is) gets serialized to JSON piecemeal and streamed out to the specified file handle.\n\nThe `CsvStreamFormatter` works in the exact same way, but outputs CSV data and has the same restrictions as the `CsvFormatter` in terms of the objects it accepts.\n\nIn many cases that won't actually offer much benefit, as the whole object must be in memory anyway.  However, it may be combined with the support for lazy iterators to have a property that produces objects lazily, say from a database query or read from some other source.\n\nConsider this example:\n\n```php\nuse Crell\\Serde\\Attributes\\SequenceField;\n\nclass ProductList\n{\n    public function __construct(\n        #[SequenceField(arrayType: Product::class)]\n        private iterable $products,\n    ) {}\n}\n\nclass Product\n{\n    public function __construct(\n        public readonly string $name,\n        public readonly string $color,\n        public readonly float $price,\n    ) {}\n}\n\n$databaseConn = ...;\n\n$callback = function() use ($databaseConn) {\n    $result = $databaseConn-\u003equery(\"SELECT name, color, price FROM products ORDER BY name\");\n\n    // Assuming $record is an associative array.\n    foreach ($result as $record) {\n        yield new Product(...$record);\n    }\n};\n\n// This is a lazy list of products, which will be pulled from the database.\n$products = new ProductList($callback());\n\n// Use the CSV formatter this time, but JsonStream works just as well.\n$s = new SerdeCommon(formatters: [new CsvStreamFormatter()]);\n\n// Write to stdout, aka, back to the browser.\n$init = FormatterStream::new(fopen('php://output', 'wb'));\n\n$result = $serde-\u003eserialize($products, format: 'csv-stream', init: $init);\n```\n\nThis setup will lazily pull records out of the database and instantiate an object from them, then lazily stream that data out to stdout.  No matter how many product records are in the database, the memory usage remains roughly constant.  (Note the database driver may do its own buffering of the entire result set, which could cause memory issues.  That's a separate matter, however.)\n\nWhile likely overkill for CSV, it can work very well for more involved objects being serialized to JSON.\n\n### TypeMaps\n\nType maps are a powerful feature of Serde that allows precise control over how objects with inheritance are serialized and deserialized.  Type Maps translate between the class of an object and some unique identifier that is included in the serialized data.\n\nIn the abstract, a Type Map is any object that implements the [`TypeMap`](src/TypeMap.php) interface.  TypeMaps may be provided as an attribute on a property, or on a class or interface, or provided to Serde when it is set up to allow for arbitrary maps.\n\nConsider the following example, which will be used for the remaining explanations of Type Maps:\n\n```php\nuse Crell\\Serde\\Attributes\\SequenceField;\n\ninterface Product {}\n\ninterface Book extends Product {}\n\nclass PaperBook implements Book\n{\n    protected string $title;\n    protected int $pages;\n}\n\nclass DigitalBook implements Book\n{\n    protected string $title;\n    protected int $bytes;\n}\n\nclass Sale\n{\n    protected Book $book;\n\n    protected float $discountRate;\n}\n\nclass Order\n{\n    protected string $orderId;\n\n    #[SequenceField(arrayType: Book::class)]\n    protected array $products;\n}\n```\n\nBoth `Sale` and `Order` reference `Book`, but that value could be a `PaperBook`, `DigitalBook`, or any other class that implements `Book`.  Type Maps provide a way for Serde to tell which concrete type it is.\n\n#### Class name maps\n\nThe simplest case of a class map is to include a `#[ClassNameTypeMap]` attribute on an object property.  For example, \n\n```php\nuse Crell\\Serde\\ClassNameTypeMap;\n\nclass Sale\n{\n    #[ClassNameTypeMap(key: 'type')]\n    protected Book $book;\n\n    protected float $discountRate;\n}\n```\n\nNow when a `Sale` is serialized, an extra property will be included named `type` that contains the class name.  So a sale on a digital book would serialize like so:\n\n```json\n{\n    \"book\": {\n        \"type\": \"Your\\\\App\\\\DigitalBook\",\n        \"title\": \"Thinking Functionally in PHP\",\n        \"bytes\": 45000\n    },\n    \"discountRate\": 0.2\n}\n```\n\nOn deserialization, the \"type\" property will be read and used to determine that the remaining values should be used to construct a `DigitalBook` instance, specifically.\n\nClass name maps have the advantage that they are very simple, and will work with any class that implements that interface, even those you haven't thought of yet.  The downside is that they put a PHP implementation detail (the class name) into the output, which may not be desirable.\n\n#### Static Maps\n\nStatic maps allow you to provide a fixed map from classes to meaningful keys.\n\n```php\nuse Crell\\Serde\\Attributes\\StaticTypeMap;\n\nclass Sale\n{\n    #[StaticTypeMap(key: 'type', map: [\n        'paper' =\u003e Book::class,\n        'ebook' =\u003e DigitalBook::class,\n    ])]\n    protected Book $book;\n\n    protected float $discountRate;\n}\n```\n\nNow, if a `Sale` object is serialized it will look like this:\n\n```json\n{\n    \"book\": {\n        \"type\": \"ebook\",\n        \"title\": \"Thinking Functionally in PHP\",\n        \"bytes\": 45000\n    },\n    \"discountRate\": 0.2\n}\n```\n\nStatic maps have the advantage of simplicity and not polluting the output with PHP-specific implementation details.  The downside is that they are static: They can only handle the classes you know about at code time, and will throw an exception if they encounter any other class.\n\n#### Type maps on collections\n\nType Maps may also be applied to array properties, either sequence or dictionary.  In that case, they will apply to all values in that collection.  For example:\n\n```php\nuse Crell\\Serde\\Attributes as Serde;\n\nclass Order\n{\n    protected string $orderId;\n\n    #[Serde\\SequenceField(arrayType: Book::class)]\n    #[Serde\\StaticTypeMap(key: 'type', map: [\n        'paper' =\u003e Book::class,\n        'ebook' =\u003e DigitalBook::class,\n    ])]\n    protected array $books;\n}\n```\n\n`$products` is an array of objects that implement `Book`, but could be either `PaperBook` or `DigitalBook`.  A serialized copy of this object may look like:\n\n```json\n{\n    \"orderId\": \"abc123\",\n    \"products\": [\n        {\n            \"type\": \"ebook\",\n            \"title\": \"Thinking Functionally in PHP\",\n            \"bytes\": 45000\n        },\n        {\n            \"type\": \"paper\",\n            \"title\": \"Category Theory for Programmers\",\n            \"pages\": 335\n        }\n    ]\n}\n```\n\nOn deserialization, the `type` property will again be used to determine the class that the rest of the properties should be hydrated into.\n\n#### Type mapped classes\n\nIn addition to putting a type map on a property, you may also place it on the class or interface that the property references.\n\n```php\nuse Crell\\Serde\\Attributes\\StaticTypeMap;\n\n#[StaticTypeMap(key: 'type', map: [\n    'paper' =\u003e Book::class,\n    'ebook' =\u003e DigitalBook::class,\n])]\ninterface Book {}\n```\n\nNow, that Type Map will apply to both `Sale::$book` and to `Order::$books` with no further work on our part.\n\nType Maps also inherit.  That means we can put a type map on `Product` instead if we wanted:\n\n```php\nuse Crell\\Serde\\Attributes\\StaticTypeMap;\n\n#[StaticTypeMap(key: 'type', map: [\n    'paper' =\u003e Book::class,\n    'ebook' =\u003e DigitalBook::class,\n    'toy' =\u003e Gadget::class,\n])]\ninterface Product {}\n```\n\nAnd both `Sale` and `Order` will still serialize with the appropriate key.\n\n#### Dynamic type maps\n\nType Maps may also be provided directly to the Serde object when it is created.  Any object that implements `TypeMap` may be used.  This is most useful when the list of possible classes is dynamic based on user configuration, database values, what plugins are installed in your application, etc.\n\n```php\nuse Crell\\Serde\\TypeMap;\n\nclass ProductTypeMap implements TypeMap\n{\n    public function __construct(protected readonly Connection $db) {}\n\n    public function keyField(): string\n    {\n        return 'type';\n    }\n\n    public function findClass(string $id): ?string\n    {\n        return $this-\u003edb-\u003esomeLookup($id);\n    }\n\n    public function findIdentifier(string $class): ?string\n    {\n        return $this-\u003edb-\u003esomeMappingLogic($class);\n    }\n}\n\n$typeMap = new ProductTypeMap($dbConnection);\n\n$serde = new SerdeCommon(typeMaps: [\n    Your\\App\\Product::class =\u003e $typeMap,\n]);\n\n$json = $serde-\u003eserialize($aBook, to: 'json');\n```\n\nIn practice, you would likely set that up via your Dependency Injection system.\n\nNote that `ClassNameTypeMap` and `StaticTypeMap` may be injected as well, as can any other class that implements `TypeMap`.\n\n#### Custom type maps\n\nYou may also write your own Type Maps as attributes.  The only requirements are:\n\n1. The class implements the `TypeMap` interface.\n2. The class is marked as an #[\\Attribute].\n3. The class is legal on *both* classes and properties. That is, `#[\\Attribute(\\Attribute::TARGET_CLASS | \\Attribute::TARGET_PROPERTY)]`\n\n### Scopes\n\nSerde supports \"scopes\" for having different versions of an attribute recognized in different contexts.\n\nAny attribute (`Field`, `TypeMap`, `SequenceField`, `DictionaryField`, `PostLoad`, etc.) may take a `scopes` argument, which accepts an array of strings.  If specified, that attribute is only valid if serializing or deserializing in that scope.  If no scoped attribute is specified, then the behavior will fall back to an unscoped attribute or an omitted attribute.\n\nFor example, given this class:\n\n```php\nclass User\n{\n    private string $username;\n\n    #[Field(exclude: true)]\n    private string $password;\n\n    #[Field(exclude: true)]\n    #[Field(scope: 'admin')]\n    private string $role;\n}\n```\n\nIf you serialize it like so:\n\n```php\n$json = $serde-\u003eserialize($user, 'json');\n```\n\nIt will result in this JSON response:\n\n```json\n{\n    \"username\": \"Larry\"\n}\n```\n\nThat's because, in an unscoped request, the first `Field` on `$role` is used, which excludes it from the output.  However, if you specify a scope:\n\n```php\n$json = $serde-\u003eserialize($user, 'json', scopes: ['admin']);\n```\n\nThen the `admin` version of `$role`'s `Field` will be used, which is not excluded, and get this result:\n\n```json\n{\n    \"username\": \"Larry\",\n    \"role\": \"Developer\"\n}\n```\n\nWhen using scopes, it may be helpful to disable automatic property inclusion and require that each be specified explicitly.  For example:\n\n```php\n#[ClassSettings(includeFieldsByDefault: false)]\nclass Product\n{\n    #[Field]\n    private int $id = 5;\n\n    #[Field]\n    #[Field(scopes: ['legacy'], serializedName: 'label')]\n    private string $name = 'Fancy widget';\n\n    #[Field(scopes: ['newsystem'])]\n    private float $price = '9.99';\n\n    #[Field(scopes: ['legacy'], serializedName: 'cost')]\n    private float $legacyPrice = 9.99;\n\n    #[Field(serializedName: 'desc')]\n    private string $description = 'A fancy widget';\n\n    private int $stock = 50;\n}\n```\n\nIf serialized with no scope specified, it will result in this:\n\n```json\n{\n    \"id\": 5,\n    \"name\": \"Fancy widget\",\n    \"desc\": \"A fancy widget\"\n}\n```\n\nAs those are the only fields that are \"in scope\" when no scope is specified.\n\nIf serialized with the `legacy` scope:\n\n```json\n{\n    \"id\": 5,\n    \"label\": \"Fancy widget\",\n    \"cost\": 9.99,\n    \"desc\": \"A fancy widget\"\n}\n```\n\nThe scope-specific `Field` on `$name` gets used instead, which changes the serialized name. The `$legacyPrice` property is also included now, but renamed to \"cost\".\n\nIf serialized with the `newsystem` scope:\n\n```json\n{\n    \"id\": 5,\n    \"name\": \"Fancy widget\",\n    \"price\": \"9.99\",\n    \"desc\": \"A fancy widget\"\n}\n```\n\nIn this case, the `$name` property uses the unscoped version of `Field`, and so is not renamed.  The string-based `$price` is now in-scope, but the float-based `$legacyPrice` is not.  Note that in none of these cases is the current `$stock` included, as it has no attribute at all.\n\nFinally, it's also possible to serialize multiple scopes simultaneously.  This is an OR operation, so any field marked for *any* specified scope will be included.\n\n```php\n$json = $serde-\u003eserialize($product, 'json', scopes: ['legacy', 'newsystem']);\n```\n\n```json\n{\n    \"id\": 5,\n    \"name\": \"Fancy widget\",\n    \"price\": \"9.99\",\n    \"cost\": 9.99,\n    \"desc\": \"A fancy widget\"\n}\n```\n\nNote that since there is both an unscoped and a scoped version of the `Field` on `$name`, the scoped one wins and the property gets renamed.\n\nIf multiple attribute variants could apply for the specified scope, the lexically first in a scope will take precedence over later ones, and a scoped attribute will take precedence over an unscoped one.\n\nNote that when deserializing, specifying a scope will exclude not only out-of-scope properties but their defaults as well.  That is, they will not be set, even to a default value, and so may be \"uninitialized.\"  That is rarely desirable, so it may be preferable to deserialize without a scope, even if a value was serialized with a scope.  That will depend on your use case.\n\nFor more on scopes, see the [AttributeUtils](https://github.com/CrellAttributeUtils#Scopes) documentation.\n\n### Validation with `#[PostLoad]`\n\nIt is important to note that when deserializing, `__construct()` is not called at all.  That means any validation present in the constructor will not be run on deserialization.\n\nInstead, Serde will look for any method or methods that have a `#[\\Crell\\Serde\\Attributes\\PostLoad]` attribute on them.  This attribute takes no arguments other than scopes.  After an object is populated, any `PostLoad` methods will be invoked with no arguments in lexical order.  The main use case for this feature is validation, in which case the method should throw an exception if the populated data is invalid in some way.  (For instance, some integer must be positive.)\n\nThe visibilty of the method is irrelevant.  Serde will call `public`, `private`, or `protected` methods the same.  Note, however, that a `private` method in a parent class of the class being deserialized to will not get called, as it is not accessible to PHP from that scope.\n\n## Extending Serde\n\nInternally, Serde has six types of extensions that work in concert to produce a serialized or deserialized product.\n\n* Type Maps, as discussed above, are optional and translate a class name to a lookup identifier and back.\n* A [`Exporter`](src/PropertyHandler/Exporter.php) is responsible for pulling values off of an object, processing them if necessary, and then passing them on to a Formatter.  This is part of the Serialization pipeline.\n* A [`Importer`](src/PropertyHandler/Importer.php) is responsible for using a Deformatter to extract data from incoming data and then translate it as necessary to be written to an object.  This is part of the Deserialization pipeline.\n* A [`Formatter`](src/Formatter/Formatter.php) is responsible for writing to a specific output format, like JSON or YAML.  This is part of the Serialization pipeline.\n* A [`Deformatter`](src/Formatter/Deformatter.php) is responsible for reading data off of an incoming format and passing it back to an `Importer`.  This is part of the Deserialization pipeline.\n* A [`TypeField`](src/TypeField.php) is a custom per-type field that can be added to a property to provide more type-specific logic to a corresponding Exporter or Importer.  In a sense, they are \"extra arguments\" to an Exporter or Importer, and if you implement a custom one you will almost certainly implement your own Exporter or Importer to go with it.  `DateTimeField`, `DictionaryField`, and `SequenceField` are examples of Type Fields.  TypeFields are also Transitive, so they can be put on a custom class to apply to anywhere that class is used on a property (unless overridden locally).\n\nCollectively, `Importer` and `Exporter` instances are called \"handlers.\"\n\nIn general, `Importer`s and `Exporter`s are *PHP-type specific*, while `Formatter`s and `Deformatter`s are *serialized-format specific*.  Custom Importers and Exporters can also declare themselves to be format-specific if they contain format-sensitive optimizations.\n\n`Importer` and `Exporter` may be implemented on the same object, or not.  Similarly, `Formatter` and `Deformatter` may be implemented together or not.  That is up to whatever seems easiest for the particular implementation, and the provided extensions do a little of each depending on the use case.\n\nThe interfaces linked above provide more precise explanations of how to use them.  In most cases, you would only need to implement a Formatter or Deformatter to support a new format.  You would only need to implement an Importer or Exporter when dealing with a specific class that needs extra special handling for whatever reason, such as its serialized representation having little or no relationship with its object representation.\n\nAs an example, a few custom handlers are included to deal with common cases.\n\n* [`DateTimeExporter`](src/PropertyHandler/DateTimeExporter.php): This object will translate `DateTime` and `DateTimeImmutable` objects to and from a serialized form as a string.  Specifically, it will use the `\\DateTimeInterface::RFC3339_EXTENDED` format for the string when serializing.  The timestamp will then appear in the serialized output as a normal string.  When deserializing, it will accept any datetime format supported by `DateTime`'s constructor.\n* [`DateTimeZoneExporter`](src/PropertyHandler/DateTimeZoneExporter.php): This object will translate `DateTimeZone` objects to and from a serialized form as a timezone string.  That is, `DateTimeZone('America/Chicago`)` will be represented in the format as the string `America/Chicago`.\n* [`NativeSerializeExporter`](src/PropertyHandler/NativeSerializeExporter.php): This object will apply to any class that has a `__serialize()` method (when serializing) or `__unserialize()` method (when deserializing).  These PHP magic methods provide alternate representations of an object intended for use with PHP's native `serialize()` and `unserialize()` methods, but can also be used for any other format.  If `__serialize()` is defined, it will be invoked and whatever associative array it returns will be written to the selected format as a dictionary.  If `__unserialize()` is defined, this object will read a dictionary from the incoming data and then pass it to that method on a newly created object, which will then be responsible for populating the object as appropriate.  No further processing will be done in either direction.\n* [`EnumOnArrayImporter`](src/PropertyHandler/EnumOnArrayImporter.php): Serde natively supports PHP Enums and can serialize them as ints or strings as appropriate.  However, in the special case of reading from a PHP array format this object will take over and support reading an Enum literal in the incoming data.  That allows, for example, a configuration array to include hand-inserted Enum values and still be cleanly imported into a typed, defined object.\n\n## Architecture diagrams\n\nSerialization works approximately like this:\n\n```mermaid\nsequenceDiagram\nparticipant Serde\nparticipant Serializer\nparticipant Exporter\nparticipant Formatter\nSerde-\u003e\u003eFormatter: initialize()\nFormatter--\u003e\u003eSerde: prepared value\nSerde-\u003e\u003eSerializer: Set up\nSerde-\u003e\u003eSerializer: serialize()\nactivate Serializer\nloop For each property\n  Serializer-\u003e\u003eExporter: call depending on type\n  Exporter-\u003e\u003eFormatter: type-specific write method\n  Formatter-\u003e\u003eSerializer: serialize() sub-value\nend\nSerializer-\u003e\u003eFormatter: finalize()\nSerializer--\u003e\u003eSerde: final value\ndeactivate Serializer\n```\n\nAnd deserialization looks very similar:\n\n```mermaid\nsequenceDiagram\nparticipant Serde\nparticipant Deserializer\nparticipant Importer\nparticipant Deformatter\nSerde-\u003e\u003eDeformatter: initialize()\nDeformatter--\u003e\u003eSerde: prepared source\nSerde-\u003e\u003eDeserializer: Set up\nSerde-\u003e\u003eDeserializer: deserialize()\nactivate Deserializer\nloop For each property\nDeserializer-\u003e\u003eImporter: call depending on type\nImporter-\u003e\u003eDeformatter: type-specific read method\nDeformatter-\u003e\u003eDeserializer: deserialize() sub-value\nend\nDeserializer-\u003e\u003eDeformatter: finalize()\nDeserializer--\u003e\u003eSerde: final value\ndeactivate Deserializer\n```\n\nIn both cases, note that nearly all behavior is controlled by a one-off serializer/deserializer object, not by Serde itself.  Serde itself is just a wrapper that configures the context for the runner object.\n\n## Dependency Injection configuration\n\nSerde is designed to be usable \"out of the box\" without any additional setup.  However, when included in a larger system it is best to configure it properly via Dependency Injection.\n\nThere are three ways you can set up Serde.\n\n1. The `SerdeCommon` class includes most available handlers and formatters out of the box, ready to go, although you can add additional ones via the constructor.\n2. The `SerdeBasic` class has no pre-built configuration whatsoever; you will need to provide all Handlers, Formatters, or Type Maps you want yourself, in the order you want them applied.\n3. You may also extend the `Serde` base class itself and create your own custom pre-made configuration, with just the Handlers or Formatters (provided or custom) that you want.\n\nBoth `SerdeCommon` and `SerdeBasic` take four arguments: The [`ClassAnalyzer`](https://github.com/Crell/AttributeUtils) to use, an array of Handlers, an array of Formatters, and an array of Type Maps.  If no analyzer is provided, Serde creates a memory-cached Analyzer by default so that it will always work.  However, in a DI configuration it is strongly recommended that you configure the Analyzer yourself, with appropriate caching, and inject that into Serde as a dependency to avoid duplicate Analyzers (and duplicate caches).  If you have multiple different Serde configurations in different services, it may also be beneficial to make all handlers and formatters services as well and explicitly inject them into `SerdeBasic` rather than relying on `SerdeCommon`.\n\n## Change log\n\nPlease see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.\n\n## Testing\n\n``` bash\n$ composer test\n```\n\n## Contributing\n\nPlease see [CONTRIBUTING](CONTRIBUTING.md) and [CODE_OF_CONDUCT](CODE_OF_CONDUCT.md) for details.\n\n## Security\n\nIf you discover any security related issues, please use the [GitHub security reporting form](https://github.com/Crell/Serde/security) rather than the issue queue.\n\n## Credits\n\n- [Larry Garfield][link-author]\n- [All Contributors][link-contributors]\n\nInitial development of this library was sponsored by [TYPO3 GmbH](https://typo3.com/).\n\n## License\n\nThe Lesser GPL version 3 or later. Please see [License File](LICENSE.md) for more information.\n\n[ico-version]: https://img.shields.io/packagist/v/Crell/Serde.svg?style=flat-square\n[ico-license]: https://img.shields.io/badge/License-LGPLv3-green.svg?style=flat-square\n[ico-downloads]: https://img.shields.io/packagist/dt/Crell/Serde.svg?style=flat-square\n\n[link-packagist]: https://packagist.org/packages/Crell/Serde\n[link-scrutinizer]: https://scrutinizer-ci.com/g/Crell/Serde/code-structure\n[link-code-quality]: https://scrutinizer-ci.com/g/Crell/Serde\n[link-downloads]: https://packagist.org/packages/Crell/Serde\n[link-author]: https://github.com/Crell\n[link-contributors]: ../../contributors\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FCrell%2FSerde","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FCrell%2FSerde","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FCrell%2FSerde/lists"}