Library for (de-)serializing data of any complexity (supports JSON, and XML)

Last update: May 19, 2022

jms/serializer

Build status

alt text

Introduction

This library allows you to (de-)serialize data of any complexity. Currently, it supports XML and JSON.

It also provides you with a rich tool-set to adapt the output to your specific needs.

Built-in features include:

  • (De-)serialize data of any complexity; circular references and complex exclusion strategies are handled gracefully.
  • Supports many built-in PHP types (such as dates, intervals)
  • Integrates with Doctrine ORM, et. al.
  • Supports versioning, e.g. for APIs
  • Configurable via XML, YAML, or Annotations

Documentation

Learn more about the serializer in its documentation.

Notes

You are browsing the code for the 3.x version, if you are interested in the 1.x or 2.x version, check the 1.x and 2.x branches.

The version 3.x is the supported version (master branch). The 1.x and 2.x versions are not supported anymore.

For the 1.x and 2.x branches there will be no additional feature releases.
Security issues will be fixed till the 1st January 2020 and only critical bugs might receive fixes until the 1st September 2019.

Instructions on how to upgrade to 3.x are available in the UPGRADING document.

Professional Support

For eventual paid support please write an email to [email protected].

GitHub

https://github.com/schmittjoh/serializer
Comments
  • 1. Change license to MIT

    In order to change the license on this project, we have to get the approval of every past contributor. At the time of writing this we have had 128 contributors.

    If you are reading this and you are a contributor on the below list, please reply to this GitHub issue with the following text:

    I, @username, agree to license my contributions to the schmittjoh/serializer project from the Apache-2.0 to the MIT license effective immediately.
    
    • [ ] @steveYeah commits code changes, already overwritten in 1.x
    • [x] @emgiezet commits changes part of the serializer-bundle already approved in the bundle MIT migration
    • [x] @wheelsandcogs commits yaml driver improvements, become part of the bundle repo and already approved in the bundle MIT migration
    • [x] @dustin10 commits doc changes, already overwritten in 1.x
    • [x] @armetiz
    • [x] @JustBlackBird
    • [x] @dbu
    • [x] @aviortm
    • [x] @ftassi
    • [x] @helmer
    • [x] @lyrixx
    • [x] @minayaserrano commits form handling improvements
    • [x] @mrosiu
    • [x] @Majkl578 commits
    • [x] @olvlvl
    • [x] @passkey1510
    • [x] @ruimarinho
    • [x] @l3l0
    • [x] @igorw
    • [x] @michelsalib
    • [x] @toby-griffiths
    • [x] @gimler
    • [x] @Soullivaneuh
    • [x] @0mars
    • [x] @dsyph3r
    • [x] @ajgarlag
    • [x] @emilien-puget
    • [x] @scasei
    • [x] @TristanMogwai
    • [x] @Strate
    • [x] @spolischook
    • [x] @ribeiropaulor
    • [x] @richardfullmer
    • [x] @jaymecd
    • [x] @cystbear
    • [x] @tarjei
    • [x] @rande
    • [x] @colinfrei
    • [x] @Potherca
    • [x] @mente
    • [x] @DavidMikeSimon
    • [x] @Lumbendil
    • [x] @wouterj
    • [x] @burki
    • [x] @rothfahl
    • [x] @xanido
    • [x] @holtkamp
    • [x] @gnat42
    • [x] @Nyholm
    • [x] @tvlooy
    • [x] @rpg600
    • [x] @phiamo
    • [x] @mikemix
    • [x] @hyperized
    • [x] @patashnik
    • [x] @romantomchak
    • [x] @gordalina
    • [x] @chasen
    • [x] @dunglas
    • [x] @greg0ire
    • [x] @adrienbrault
    • [x] @stof
    • [x] @tystr
    • [x] @scaytrase
    • [x] @xoob
    • [x] @jhkchan
    • [x] @vicb
    • [x] @xabbuh
    • [x] @bblue
    • [x] @mpajunen
    • [x] @hacfi
    • [x] @lsmith77
    • [x] @yethee
    • [x] @anyx
    • [x] @jonotron
    • [x] @ruudk
    • [x] @Aliance
    • [x] @robocoder
    • [x] @andy-morgan
    • [x] @tmilos
    • [x] @leonnleite
    • [x] @urakozz
    • [x] @develth
    • [x] @AlexKovalevych
    • [x] @smurfy
    • [x] @zerkms
    • [x] @tyler-sommer
    • [x] @bobvandevijver
    • [x] @veloxy
    • [x] @prosalov
    • [x] @goetas
    • [x] @schmittjoh
    • [x] @chregu
    • [x] @alcalyn
    • [x] @fdyckhoff
    • [x] @megazoll
    • [x] @bburnichon
    • [x] @akoebbe
    • [x] @dragosprotung
    • [x] @rosstuck
    • [x] @josser
    • [x] @Seldaek
    • [x] @jockri
    • [x] @BraisGabin
    • [x] @Bukashk0zzz
    • [x] @arghav
    • [x] @scrutinizer-auto-fixer (@schmittjoh bot)
    • [x] @bertterheide
    • [x] @chrisjohnson00
    • [x] @jeserkin
    • [x] @c0ntax
    • [x] @aledeg
    • [x] @guilhermeblanco
    • [x] @willdurand
    • [x] @HarmenM
    • [x] @carusogabriel
    • [x] @iambrosi
    • [x] @zebraf1
    • [x] @jgendera
    • [x] @enumag
    • [x] @kriswallsmith
    • [x] @LeaklessGfy
    • [x] @marcospassos
    • [x] @MDrollette
    • [x] @inanimatt
    • [x] @JMSBot (@schmittjoh bot)
    • [x] @mvanmeerbeck
    • [x] @colinmorelli
    Reviewed by goetas at 2018-05-16 16:20
  • 2. Generate namespaced element on XmlList entries

    Things done with this patch:

    • Merged #237
    • Added documentation for #237
    • @XmlNamespace(uri="http://example.com/namespace") acts as default namespace
    • XmlDeserializationVisitor
      • Removed a lot of XPath stuff in flavor of simpler SimpleXMLElement::children method
      • Added $objectMetadataStack to access class metadata from a property
    • XmlSerializationVisitor
      • Removed unnecessary element prefixing when the document/node default namespace match the desired namesapce
      • Removed some duplicated code, adding createElement and setAttributeOnNode methods
    Reviewed by goetas at 2014-07-16 10:12
  • 3. Adds XML namespaces support

    I've tried to implement the XML namespaces support for serialization and deserialization.

    Please, review it if you can and tell me what you think about.

    Reviewed by ajgarlag at 2013-03-08 17:40
  • 4. Add metadata informations

    Hi, I'm using jms serializer (bundle) in a symfony project. For now, I'm serializing an array of object and it's all ok.

    [
    {"prop": "value"},
    {"prop": "value"},
    {"prop": "value"}
    ]
    

    But I need to add information about the pagination (I fetch data with doctrine) to end up to have a json like this:

    [
    "meta": {"page": 2, "total": 3, "per_page": 2},
    "results": [
       {"prop": "value"},
       {"prop": "value"},
       {"prop": "value"}
       ]
    ]
    

    As you can see, I need to add the informations about total items, current pagination page and the number of the elements in the page.

    I have created a PostSerializationSubscriber that implements JMS\Serializer\EventDispatcher\EventSubscriberInterface and I have implemented the getSubscribedEvents() method like this:

        public static function getSubscribedEvents()
        {
            return [
                [
                    'event' => 'serializer.post_serialize',
                    'method' => 'onPostSerialize'
                ],
            ];
        }
    

    how can I edit my serialized object in onPostSerialize() method?

        public function onPostSerialize(ObjectEvent $postSerializeEvent)
        {
            $postSerializeEvent->getContext()->getVisitor()->addData('someKey','someValue');
        }
    

    The code below, add 'someKey','someValue' for each elements... but it is not what I want

    Thanks

    Reviewed by matiux at 2016-08-29 15:57
  • 5. Handle array format for dateHandler

    | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | Doc updated | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | | License | MIT

    Reviewed by VincentLanglet at 2019-07-14 23:14
  • 6. Serialization of nested polymorphic objects

    Serializer has trouble with nested objects of polymorphic types; it serializes them as their stated base type in an annotation, rather than their real type.

    This PR adds tests which exhibit this bug, then adds an additional pre-serialize event subscriber that corrects the issue.

    Reviewed by DavidMikeSimon at 2014-02-18 17:38
  • 7. Allow Constructed Object to be Passed to Deserialize

    It doesn't appear that there's a way to give an already constructed object to the deserialize method (in which case the serializer would just skip the object construction phase and begin mapping properties).

    My use case is simple: In a REST API, I want to allow a user to create or update objects. Each type of operation (create, update, delete, read) has a different set of security rules. However, with the current implementation of the deserializer (in which we are using the Doctrine object constructor), the user can issue a create request, but specify an "id" as part of the payload. The Doctrine object constructor will then return a mapped entity before deserialization begins, allowing the end user to perform an update (while the server still thinks it's doing a create).

    Also, on this point: I was considering making a separate object constructor - but I need to give some attributes to the object constructor on each use (such as the ID which it should construct an object for). Right now, the only way to get those attributes to the deserializer is to include them in the payload string that gets passed to it - which is precisely what I'd like to avoid.

    There are ways to handle this in the actual REST endpoint, but none of them are really ideal.What I'd like to be able to do is pass an already-constructed object (which my REST endpoint will handle fetching) to the deserializer and just have it do it's property mapping onto the object. This way, I can always pass a blank object in create calls, while I can pass a constructed object in update calls.

    Would this be possible? I could submit a PR if this is something you'd be willing to implement.

    Reviewed by cmorelli at 2013-04-14 14:29
  • 8. Resolve collections from DocBlock

    | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | Doc updated | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | https://github.com/schmittjoh/serializer/issues/1208 | License | MIT

    There are few things worth discussion:

    1. What should happen, when incorrect DocBlock type is given?

      /**
      * @var \stdClass
      */
      public array $productIds;
      

    Current implementation will throw exception, as above has no sense

    1. What should happen, when union type is given?

       /**
       * @var ProductName[]|ProductDescription[]
       */
      public array $productIds;
      

    Current implementation will throw exception, as we will not know, which class should be used for deserialization.

    1. What should happen, when class does not exists?

       /**
       * @var NotExistingClass[]
       */
      public array $productIds;
      

    Current implementation will throw exception as, this class will not be deserializable.

    1. If exception is thrown in any case of 1) and 2) 3) what exception type it should be?

    Current implementation will throw \InvalidArgumentException

    Reviewed by dgafka at 2020-06-14 15:02
  • 9. Add DateTimeImmutable support to DateHandler

    Hello The actual DateHandler support DateTime and DateInterval types. Would you consider adding DateTimeImmutable support for this handler ? I can do a Pr if you like the idea.

    Regards,

    @mathroc

    Reviewed by wI2L at 2015-12-29 14:20
  • 10. The sub-class "Proxy-Class" is not listed in the discriminator of the base class "DiscriminatorClass"

    I have a strange Problem here,

    im getting this Error on serialization:

    [LogicException]
    The sub-class "Dl\DomainModel\Proxies\__CG__\Dl\Component\DomainModel\Product\Attribute\EntityAttribute" is not listed in the discriminator of the base class "Dl\Component\DomainModel\Product\Attribute\AbstractAttribute".
    

    it happens during serialization:

     [file]: string (57) "/www/vendor/jms/metadata/src/Metadata/MetadataFactory.php"
     [line]: int 150
     [function]: string (5) "merge"
     [class]: string (37) "JMS\Serializer\Metadata\ClassMetadata"
     [type]: string (2) "->"
     [args]: array (1)
      · [0]: object(JMS\Serializer\Metadata\ClassMetadata)
    
     [1]: array (6)
     [file]: string (57) "/www/vendor/jms/metadata/src/Metadata/MetadataFactory.php"
     [line]: int 105
     [function]: string (16) "addClassMetadata"
     [class]: string (24) "Metadata\MetadataFactory"
     [type]: string (2) "->"
     [args]: array (2)
      · [0]: object(JMS\Serializer\Metadata\ClassMetadata)
      · [1]: object(JMS\Serializer\Metadata\ClassMetadata)
    
     [2]: array (6)
     [file]: string (64) "/www/vendor/jms/serializer/src/JMS/Serializer/GraphNavigator.php"
     [line]: int 188
     [function]: string (19) "getMetadataForClass"
     [class]: string (24) "Metadata\MetadataFactory"
     [type]: string (2) "->"
     [args]: array (1)
      · [0]: string (88) "Dl\DomainModel\Proxies\__CG__\Dl\Component\DomainModel\Product\Attribute\EntityAttribute"
    
     [3]: array (6)
     [file]: string (77) "/www/vendor/jms/serializer/src/JMS/Serializer/GenericSerializationVisitor.php"
     [line]: int 140
     [function]: string (6) "accept"
     [class]: string (29) "JMS\Serializer\GraphNavigator"
     [type]: string (2) "->"
     [args]: array (3)
      · [0]: object(Dl\DomainModel\Proxies\__CG__\Dl\Component\DomainModel\Product\Attribute\EntityAttribute)
      · [1]: array (2)
      ·  · [name]: string (88) "Dl\DomainModel\Proxies\__CG__\Dl\Component\DomainModel\Product\Attribute\EntityAttribute"
      ·  · [params]: array (0)
    
      · [2]: object(JMS\Serializer\SerializationContext)
    
     [4]: array (6)
     [file]: string (64) "/www/vendor/jms/serializer/src/JMS/Serializer/GraphNavigator.php"
     [line]: int 235
     [function]: string (13) "visitProperty"
     [class]: string (42) "JMS\Serializer\GenericSerializationVisitor"
     [type]: string (2) "->"
     [args]: array (3)
      · [0]: object(JMS\Serializer\Metadata\PropertyMetadata)
      · [1]: object(Dl\Component\DomainModel\Partner\UnmatchedValue)
      · [2]: object(JMS\Serializer\SerializationContext)
    
     [5]: array (6)
     [file]: string (77) "/www/vendor/jms/serializer/src/JMS/Serializer/GenericSerializationVisitor.php"
     [line]: int 102
     [function]: string (6) "accept"
     [class]: string (29) "JMS\Serializer\GraphNavigator"
     [type]: string (2) "->"
     [args]: array (3)
      · [0]: object(Dl\Component\DomainModel\Partner\UnmatchedValue)
      · [1]: array (2)
      ·  · [name]: string (47) "Dl\Component\DomainModel\Partner\UnmatchedValue"
      ·  · [params]: array (0)
    
      · [2]: object(JMS\Serializer\SerializationContext)
    
     [6]: array (6)
     [file]: string (74) "/www/vendor/jms/serializer/src/JMS/Serializer/JsonSerializationVisitor.php"
     [line]: int 55
     [function]: string (10) "visitArray"
     [class]: string (42) "JMS\Serializer\GenericSerializationVisitor"
     [type]: string (2) "->"
     [args]: array (3)
      · [0]: object(Dl\Component\Helpers\DataType\ArrayCollection)
      · [1]: array (2)
      ·  · [name]: string (5) "array"
      ·  · [params]: array (1)
      ·  ·  · [0]: array (2)
      ·  ·  ·  · [name]: string (47) "Dl\Component\DomainModel\Partner\UnmatchedValue"
      ·  ·  ·  · [params]: array (0)
    
      · [2]: object(JMS\Serializer\SerializationContext)
    
     [7]: array (6)
     [file]: string (64) "/www/vendor/jms/serializer/src/JMS/Serializer/GraphNavigator.php"
     [line]: int 129
     [function]: string (10) "visitArray"
     [class]: string (39) "JMS\Serializer\JsonSerializationVisitor"
     [type]: string (2) "->"
     [args]: array (3)
      · [0]: object(Dl\Component\Helpers\DataType\ArrayCollection)
      · [1]: array (2)
      ·  · [name]: string (5) "array"
      ·  · [params]: array (1)
      ·  ·  · [0]: array (2)
      ·  ·  ·  · [name]: string (47) "Dl\Component\DomainModel\Partner\UnmatchedValue"
      ·  ·  ·  · [params]: array (0)
    
      · [2]: object(JMS\Serializer\SerializationContext)
    
     [8]: array (6)
     [file]: string (77) "/www/vendor/jms/serializer/src/JMS/Serializer/GenericSerializationVisitor.php"
     [line]: int 140
     [function]: string (6) "accept"
     [class]: string (29) "JMS\Serializer\GraphNavigator"
     [type]: string (2) "->"
     [args]: array (3)
      · [0]: object(Dl\Component\Helpers\DataType\ArrayCollection)
      · [1]: array (2)
      ·  · [name]: string (5) "array"
      ·  · [params]: array (1)
      ·  ·  · [0]: array (2)
      ·  ·  ·  · [name]: string (47) "Dl\Component\DomainModel\Partner\UnmatchedValue"
      ·  ·  ·  · [params]: array (0)
    
      · [2]: object(JMS\Serializer\SerializationContext)
    
     [9]: array (6)
     [file]: string (64) "/www/vendor/jms/serializer/src/JMS/Serializer/GraphNavigator.php"
     [line]: int 235
     [function]: string (13) "visitProperty"
     [class]: string (42) "JMS\Serializer\GenericSerializationVisitor"
     [type]: string (2) "->"
     [args]: array (3)
      · [0]: object(JMS\Serializer\Metadata\PropertyMetadata)
      · [1]: object(Dl\Import\Task\PersistTask)
      · [2]: object(JMS\Serializer\SerializationContext)
    
     [10]: array (6)
     [file]: string (60) "/www/vendor/jms/serializer/src/JMS/Serializer/Serializer.php"
     [line]: int 176
     [function]: string (6) "accept"
     [class]: string (29) "JMS\Serializer\GraphNavigator"
     [type]: string (2) "->"
     [args]: array (3)
      · [0]: object(Dl\Import\Task\PersistTask)
      · [1]: array (2)
      ·  · [name]: string (26) "Dl\Import\Task\PersistTask"
      ·  · [params]: array (0)
    
      · [2]: object(JMS\Serializer\SerializationContext)
    
     [11]: array (6)
     [file]: string (60) "/www/vendor/jms/serializer/src/JMS/Serializer/Serializer.php"
     [line]: int 82
     [function]: string (5) "visit"
     [class]: string (25) "JMS\Serializer\Serializer"
     [type]: string (2) "->"
     [args]: array (4)
      · [0]: object(JMS\Serializer\JsonSerializationVisitor)
      · [1]: object(JMS\Serializer\SerializationContext)
      · [2]: object(Dl\Import\Task\PersistTask)
      · [3]: string (4) "json"
    
     [12]: array (2)
     [function]: string (44) "Closure$JMS\Serializer\Serializer::serialize"
     [args]: array (1)
      · [0]: object(JMS\Serializer\JsonSerializationVisitor)
    
     [13]: array (4)
     [file]: string (54) "/www/vendor/phpoption/phpoption/src/PhpOption/Some.php"
     [line]: int 89
     [function]: string (14) "call_user_func"
     [args]: array (2)
      · [0]: object(Closure$JMS\Serializer\Serializer::serialize;1748757503)
      · [1]: object(JMS\Serializer\JsonSerializationVisitor)
    
     [14]: array (6)
     [file]: string (60) "/www/vendor/jms/serializer/src/JMS/Serializer/Serializer.php"
     [line]: int 85
     [function]: string (3) "map"
     [class]: string (14) "PhpOption\Some"
     [type]: string (2) "->"
     [args]: array (1)
      · [0]: object(Closure$JMS\Serializer\Serializer::serialize;1748757503)
    
    

    anyone have a clue why this occurs?

    Reviewed by digitalkaoz at 2015-06-08 14:05
  • 11. Default conversion of camelCase to underscores is counterintuitive

    
    <?php
    
    class Thing
    {
        /**
         * @JMS\Type("integer")
         */
        private $fooBar;
    
        public function getFooBar()
        {
            return $this->fooBar;
        }
    }
    
    $json = '{"fooBar": 123}';
    $deserializedThing = $serializer->deserialize($json, 'Thing', 'json');
    
    assert(123, $deserializedThing->getFooBar()); 
    // FAILS, we get null instead
    // Adding @JMS\SerializedName("fooBar") resolves the issue
    // Internally, the serializer converts fooBar to foo_bar unless instructed otherwise
    
    

    This behavior is documented under @SerializedName http://jmsyst.com/libs/serializer/master/reference/annotations

    However, this is so counterintuitive, that I think it is a bug. In the example above, there's absolutely no reason why the serializer should interfere with my naming, since both the json and the php code use camelcase.

    Reviewed by mathiasverraes at 2013-02-07 12:46
  • 12. PoC - optimise memory usage for json serialisation.

    | Q | A | ------------- | --- | Bug fix? | no | New feature? | no | Doc updated | no | BC breaks? | no | Deprecations? | no | Tests pass? | no | Fixed tickets | #... | License | MIT

    Current issue:

    JsonSerialiser is using 3 times more memory compared to XML serialisation:

    +--------------------------------+--------------------+-----+------+-----+----------+--------+--------+
    | benchmark                      | subject            | set | revs | its | mem_peak | mode   | rstdev |
    +--------------------------------+--------------------+-----+------+-----+----------+--------+--------+
    | JsonSerializationBench         | benchSerialization |     | 1    | 3   | 45.634mb | 4.692s | ±0.87% |
    | XmlSerializationBench          | benchSerialization |     | 1    | 3   | 15.898mb | 7.0s   | ±1.23% |
    | JsonMaxDepthSerializationBench | benchSerialization |     | 1    | 3   | 45.644mb | 5.578s | ±0.52% |
    +--------------------------------+--------------------+-----+------+-----+----------+--------+--------+
    

    Currently data is transformed to array structure first, next it is passed to json_encode method - so basically we need to have same data in 3 places (original object, array structure, json).

    Possible fix:

    We can try to limit amount of data passed to array by using JsonSerializable that provides nice way to prepare data in small batches. Possible cut points is objects or arrays - I've used array as it was simpler for PoC.

    Results: - memory went down from 45MB to 13MB. It does not affected execution time.

    +--------------------------------+--------------------+-----+------+-----+----------+--------+--------+
    | benchmark                      | subject            | set | revs | its | mem_peak | mode   | rstdev |
    +--------------------------------+--------------------+-----+------+-----+----------+--------+--------+
    | JsonSerializationBench         | benchSerialization |     | 1    | 3   | 13.002mb | 3.735s | ±0.95% |
    | XmlSerializationBench          | benchSerialization |     | 1    | 3   | 15.898mb | 5.555s | ±0.48% |
    | JsonMaxDepthSerializationBench | benchSerialization |     | 1    | 3   | 13.012mb | 4.336s | ±0.61% |
    +--------------------------------+--------------------+-----+------+-----+----------+--------+--------+
    

    Issues that needs to be solved:

    • [ ] exclusions based on the depth / path - we are loosing metadata stack
    • [ ] exclusions of empty arrays
    • [ ] circular reference (Segmentation fault)
    • [ ] support for iterators

    But, what is bit unexpected, the rest seems to be working nice. Can you see any potential pain points for such solution? Do you think that it is worth to invest more time to improve it?

    Best, scyzoryck

    Reviewed by scyzoryck at 2022-05-12 19:34
  • 13. Fixed decimal type mapping to match the actual type from doctrine

    | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | Doc updated | no | BC breaks? | yes | Deprecations? | no | Tests pass? | yes | Fixed tickets | | License | MIT

    Fixes #1398

    Reviewed by LANGERGabrielle at 2022-05-11 12:30
  • 14. Feature/uninitialize property exception

    | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | Doc updated | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #1367 | License | MIT

    Catch uninitialized property PHP Error and convert it into a UninitializedPropertyException if the Error message match the defined regex.

    Reviewed by pag1903 at 2022-04-28 10:23
  • 15. Handling of XML null/nil values based on namespace prefix existence

    | Q | A | ---------------- | ----- | Bug report? | no | Feature request? | yes | BC Break report? | no | RFC? | yes/no

    I've come across a problem regarding the handling of null/nil values. This is related to XML de-/serialization functionality.

    An xml node is with the current implementation generally considered to be null, when not present or when the xsi:nil="true" attribute is present.

    Let me first describe the situation I encountered, the problem(s) this raises and after that probable solutions.

    Steps required to reproduce the problem

    Take an xml document similar to the following one:

    <?xml version="1.0" encoding="utf-8"?>
    <calendar>
      <entry>
        <date>2022-04-07</date>
        <time>22:00:00</time>
      </entry>
      <entry>
        <date>2022-04-01</date>
      </entry>
      <entry>
        <date>2022-04-07</date>
        <!-- empty element, no xsi:nil="true" attribute -->
        <time/>
      </entry>
    </calendar>
    

    I'll omit the serializer configuration and php objects, as this is only an example. The important thing is that both, the date and time properties of a calendar entry are to be deserialized as DateTimeImmutable or DateTime. The date/time format is pretty obvious here (Y-m-d and H:i:s respectively). The time is nullable, where the date property is not.

    The result and what it means

    Before investigating, I would've expected this xml to produce a valid object structure where the time of the last calendar entry is simply not set. Instead, this or anything like it, will present you with an exception Invalid datetime "", expected the format "H:i:s", [...]. and so on.

    I've found out, that this was missing the xsi:nil="true" attribute to be considered null.

    Again, it's generally assumed, that a node is only null, when not present or said attribute is set. That's due to that in xml, there is pretty much no real null, as we have it in php.

    Well fine, some would say, just add that attribute in there. Would be a easy solution, indeed. Sadly, I've no direct control over the xml structure and serialization, e.g. I can't set the attribute. Ok, fine, I'm not picky, so a custom handler will do! And indeed it would (and did) do the job as needed eventually.

    But this got me thinking: So why would the serializer try to look for the xsi:nil attribute, when there is no xsi prefix bound to any namespace. An xml is invalid, if xsi:* is used without being registered. Verifiable via the w3schools xml validator using the following xml:

    <?xml version="1.0" encoding="utf-8"?>
    <calendar>
      <entry>
        <date>2022-04-07</date>
        <time>22:00:00</time>
      </entry>
      <entry>
        <date>2022-04-01</date>
      </entry>
      <entry>
        <date>2022-04-07</date>
        <time xsi:nil="true"/>
      </entry>
    </calendar>
    
    Namespace prefix xsi for nil on time is not defined
    

    Again, this is hypothetical.

    It's obviously required to add that namespace prefix and bind it somewhere in the document. But I would argue, it's not an uncommon use-case to have no namespaces defined at all. So the (kind of) requirement to add that namespace and use the attribute to be able to have an empty node be considered null is rather unintuitive. Or at least don't use the xsi:nil check if there is no xsi prefix anyway.

    What's your opinion on this?

    I've seen the way it is done with the serialization. There, the namespace is only added if any null-element has been visited.

    • https://github.com/schmittjoh/serializer/blob/91652cc27bfe53f5d9b929dee10639e611f9ade4/src/XmlSerializationVisitor.php#L386-L392

    I could think of something similar regarding the null handling to decide, depending on the presence of the namespace prefix in the document, if the xsi:nil is checked or some other logic should be used. There are several ways to allow the developer to influence the behavior.

    To be completely clear: I'm not proposing to changing the behavior and replacing it with something that would violate the spec. I'm simply saying there are use-cases where the current handling could cause issues (as it did for me). And in my opinion this is a functionality, that could benefit such use-cases.

    This is especially the case for any of the datetime types, as seen in the example above. An exception will certainly get thrown on empty elements of that type, that's in the nature of the DateHandler.

    Certainly, the xml-owner could fix their serializer or whatever they use to produce the documents, but in a real-world scenario, I find this unlikely, especially, when there are other implications such a change could have. For example on other interfacing software, that are already accustomed to the shortcomings of such system.

    What would be affected

    • https://github.com/schmittjoh/serializer/blob/91652cc27bfe53f5d9b929dee10639e611f9ade4/src/XmlDeserializationVisitor.php#L495-L501
    • https://github.com/schmittjoh/serializer/blob/91652cc27bfe53f5d9b929dee10639e611f9ade4/src/Handler/DateHandler.php#L143-L148

    Some reading

    I've gone through these references prior to deciding to open a new issue here.

    • #551
    • #803
    • https://stackoverflow.com/a/774234/2557685
    • https://stackoverflow.com/a/7250336/2557685
    • https://stackoverflow.com/a/36732347/2557685
    • http://www.dimuthu.org/blog/2008/08/18/xml-schema-nillabletrue-vs-minoccurs0/

    I'm willing to PR this, if the feature is considered beneficial. Although, some implementation details would've to be discussed prior to that.

    Best Regards Marcel

    Reviewed by machinateur at 2022-04-07 16:35
  • 16. Allow interfaces in event subscriber event config array

    | Q | A | ---------------- | ----- | Bug report? | no | Feature request? | yes | BC Break report? | no | RFC? | yes

    Event subscribers method getSubscribedEvents can define return a class entry within the events config. It would be nice if this entry also works for interfaces (or parent classes)

    Steps required to reproduce the problem

    class FooEventSubscriber implements \JMS\Serializer\EventDispatcher\EventSubscriberInterface
    {
        public static function getSubscribedEvents()
        {
            return [
                [
                    'class' => FooInterface::class
                    'event' => 'serializer.pre_serialize',
                    'format' => 'xml',
                    'method' => 'bar',
                ],
            ];
        }
    
        public function bar(PreSerializeEvent $event) {}
    }
    
    interface FooInterface {}
    class Foo implements FooInterface {}
    class Baz implements FooInterface {}
    
    1. initiate the serializer with FooEventSubscriber
    2. Serialize an instance of Foo or Baz

    Expected Result

    • The method FooEventSubscriber::bar is called on serialization

    Actual Result

    • The method is not called.
    Reviewed by proggeler at 2022-03-22 10:30
:lipstick: Scalable and durable all-purpose data import library for publishing APIs and SDKs.
:lipstick: Scalable and durable all-purpose data import library for publishing APIs and SDKs.

Porter Scalable and durable data imports for publishing and consuming APIs Porter is the all-purpose PHP data importer. She fetches data from anywhere

May 26, 2022
Map nested JSON structures onto PHP classes

JsonMapper - map nested JSON structures onto PHP classes Takes data retrieved from a JSON web service and converts them into nested object and arrays

May 13, 2022
JsonMapper - map nested JSON structures onto PHP classes

Takes data retrieved from a JSON web service and converts them into nested object and arrays - using your own model classes.

May 10, 2021
A repository with implementations of different data structures and algorithms using PHP

PHP Data Structures and Algorithms Data structure and Algorithm is always important for any programming language. PHP, being one of the most popular l

May 17, 2022
Output complex, flexible, AJAX/RESTful data structures.

Fractal Fractal provides a presentation and transformation layer for complex data output, the like found in RESTful APIs, and works really well with J

May 24, 2022
Changeset calculator between two states of a data

Totem \\\\//// |.)(.| | || | Changeset calculator between two state of a data \(__)/ Requires PHP 5.4 ; Compatible

May 6, 2021
Missing data types for PHP. Highly extendable.
Missing data types for PHP. Highly extendable.

Neverending data validation can be exhausting. Either you have to validate your data over and over again in every function you use it, or you have to rely it has already been validated somewhere else and risk potential problems.

Feb 7, 2022
A Collections library for PHP.

A Library of Collections for OO Programming While developing and helping others develop PHP applications I noticed the trend to use PHP's arrays in ne

May 2, 2022
PHP Integrated Query, a real LINQ library for PHP

PHP Integrated Query - Official site What is PINQ? Based off the .NET's LINQ (Language integrated query), PINQ unifies querying across arrays/iterator

May 3, 2022
🗃 Array manipulation library for PHP, called Arrayy!

?? Arrayy A PHP array manipulation library. Compatible with PHP 7+ & PHP 8+ \Arrayy\Type\StringCollection::create(['Array', 'Array'])->unique()->appen

May 8, 2022
[READ-ONLY] Collection library in CakePHP. This repo is a split of the main code that can be found in https://github.com/cakephp/cakephp

CakePHP Collection Library The collection classes provide a set of tools to manipulate arrays or Traversable objects. If you have ever used underscore

Mar 28, 2022
Collections Abstraction library for PHP

Collections Collections Abstraction library for PHP The Collection library is one of the most useful things that many modern languages has, but for so

Dec 27, 2021
CRUDlex is an easy to use CRUD generator for Symfony 4 and Silex 2 which is great for auto generated admin pages
CRUDlex is an easy to use CRUD generator for Symfony 4 and Silex 2 which is great for auto generated admin pages

CRUDlex CRUDlex is an easy to use, well documented and tested CRUD generator for Symfony 4 and Silex 2. It is very useful to generate admin pages for

Nov 19, 2021
Leetcode for PHP, five questions a week and weekends are updated irregularly
Leetcode for PHP,  five questions a week and weekends are updated irregularly

✏️ Leetcode for PHP why do you have to sleep for a long time ,and naturally sleep after death 联系 说明 由于目前工作主要是 golang,我又新起了一个LeetCode-Go-Week项目,- Leetc

May 26, 2022
True asynchronous PHP I/O and HTTP without frameworks, extensions, or annoying code. Uses the accepted Fibers RFC to be implemented into PHP 8.1
True asynchronous PHP I/O and HTTP without frameworks, extensions, or annoying code. Uses the accepted Fibers RFC to be implemented into PHP 8.1

PHP Fibers - Async Examples Without External Dependencies True asynchronous PHP I/O and HTTP without frameworks, extensions, or annoying code behemoth

May 13, 2022
Best FlexForm based content elements since 2012. With TCA mapping feature, simple backend view and much more features which makes it super easy to create own content element types.
Best FlexForm based content elements since 2012. With TCA mapping feature, simple backend view and much more features which makes it super easy to create own content element types.

DCE-Extension for TYPO3 What is DCE? DCE is an extension for TYPO3 CMS, which creates easily and fast dynamic content elements. Based on Extbase, Flui

Apr 8, 2022
JSONFinder - a library that can find json values in a mixed text or html documents, can filter and search the json tree, and converts php objects to json without 'ext-json' extension.

JSONFinder - a library that can find json values in a mixed text or html documents, can filter and search the json tree, and converts php objects to json without 'ext-json' extension.

Apr 15, 2022
Config is a file configuration loader that supports PHP, INI, XML, JSON, YML, Properties and serialized files and string

Config Config is a file configuration loader that supports PHP, INI, XML, JSON, YML, Properties and serialized files and strings. Requirements Config

May 14, 2022
World countries in JSON, CSV, XML and Yaml. Any help is welcome!

World countries in JSON, CSV, XML and YAML. Countries data This repository contains a list of world countries, as defined by ISO Standard 3166-1, in J

May 26, 2022