schmittjoh / serializer

Library for (de-)serializing data of any complexity (supports JSON, and XML)
http://jmsyst.com/libs/serializer
MIT License
2.33k stars 587 forks source link

Cannot deserialize non-scalar child element; always null/empty #1258

Closed mijgame closed 3 years ago

mijgame commented 3 years ago
Q A
Bug report? no(?)
Feature request? no
BC Break report? no
RFC? no

Hi.

I'm currently running into an issue deserializing a part of an XML file. Arrays and scalars seem to work just fine (based on XmlList, XmlValue or XmlAttribute). I run into an issue however when I try to deserialize a class (not directly a scalar value). The result is always deserialized to null. I have notices that when I annotate the extensionElement with @XmlValue or @Inline, they seem to be instantiated. Their contents are always null however (and I suppose that makes sense, as we want the child to have it's own context).

It's unclear to me whether I am actually missing an attribute, or that there is something wrong with the way I am handling the namespaces.

The XML I'm trying to deserialize:

<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:fluend="http://fluend.nl/ns" id="sample-diagram" targetNamespace="http://bpmn.io/schema/bpmn" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd">
  <bpmn2:process id="Process_1" isExecutable="false">
    <bpmn2:startEvent id="StartEvent_1">
      <bpmn2:outgoing>Flow_0a1pdsz</bpmn2:outgoing>
    </bpmn2:startEvent>
    <bpmn2:task id="Activity_0nj8lki" name="Set private email on user">
      <bpmn2:extensionElements>
        <fluend:attributes>
          <fluend:mapping source="id" destination="1" matchOn="true" />
          <fluend:mapping source="email" destination="4" matchOn="false" />
        </fluend:attributes>
      </bpmn2:extensionElements>
      <bpmn2:incoming>Flow_0a1pdsz</bpmn2:incoming>
      <bpmn2:outgoing>Flow_0kyt5t7</bpmn2:outgoing>
    </bpmn2:task>
    <bpmn2:sequenceFlow id="Flow_0a1pdsz" sourceRef="StartEvent_1" targetRef="Activity_0nj8lki" />
    <bpmn2:endEvent id="Event_16527o1">
      <bpmn2:incoming>Flow_0kyt5t7</bpmn2:incoming>
    </bpmn2:endEvent>
    <bpmn2:sequenceFlow id="Flow_0kyt5t7" sourceRef="Activity_0nj8lki" targetRef="Event_16527o1" />
  </bpmn2:process>
</bpmn2:definitions>

This is the part of the XML I'm having trouble with:

<bpmn2:task id="Activity_0nj8lki" name="Set private email on user">
      <bpmn2:extensionElements>
        <fluend:attributes>
          <fluend:mapping source="id" destination="1" matchOn="true" />
          <fluend:mapping source="email" destination="4" matchOn="false" />
        </fluend:attributes>
      </bpmn2:extensionElements>
</bpmn2:task>

This is the class definition for Task:

class Task extends Node
{
    /**
     * @JMS\Type("string")
     * @JMS\XmlAttribute
     * @var string
     */
    public $id;

    /**
     * @JMS\Type("string")
     * @JMS\XmlAttribute
     * @var string
     */
    public $name;

    /**
     * @JMS\Type("array<Fluend\Core\Bpmn\Deserialization\Incoming>")
     * @JMS\XmlList(inline=true,entry="outgoing",skipWhenEmpty=false)
     * @var Incoming[]
     */
    public $incoming;

    /**
     * @JMS\Type("array<Fluend\Core\Bpmn\Deserialization\Outgoing>")
     * @JMS\XmlList(inline=true,entry="outgoing",skipWhenEmpty=false)
     * @var Outgoing[]
     */
    public $outgoing;

    /**
     * @JMS\SerializedName("extensionElements")
     * @JMS\Type("Fluend\Core\Bpmn\Deserialization\Extensions")
     * @var Extensions
     */
    public $extensions;
}

The parent class, Node, is used to specify the namespace for every type of element:

/**
 * @JMS\XmlNamespace("http://www.omg.org/spec/BPMN/20100524/MODEL")
 * @JMS\XmlNamespace("http://fluend.nl/ns", prefix="fluend")
 *
 * Class Node
 * @package Fluend\Core\Bpmn\Deserialization
 */
class Node
{
}

The Extensions class (which should contain the values in extensionElements) is defined as follows:

class Extensions extends Node
{
    /**
     * @JMS\Type("Fluend\Core\Bpmn\Deserialization\Attributes")
     * @var Attributes
     */
    public $attributes;
}

The serializer is configured as follows:

 AnnotationRegistry::registerLoader('class_exists');

$builder = SerializerBuilder::create();
$builder->addDefaultHandlers();
$builder->setDebug(true);
$builder->addDefaultSerializationVisitors();
$builder->addDefaultDeserializationVisitors();
$builder->addDefaultListeners();
$builder->configureListeners(function (EventDispatcher $dispatcher) {
    $dispatcher->addSubscriber(new DebugSubscriber());
});
$builder->setPropertyNamingStrategy(
    new SerializedNameAnnotationStrategy(
        new IdenticalPropertyNamingStrategy()
    )
);

$serializer = $builder->build();

In the above configuration, $extensions is always null. Do you have any suggestions about what I could be doing wrong? Please let me know if you need anything else.

Thanks in advance.

goetas commented 3 years ago

there is notthing indicating the Namespace for the $attribute variable.

The @JMS\XmlNamespace annotations are used only for the root object, after that the namespace declaration on the specific property is used.

mijgame commented 3 years ago

Hi goetas,

The XML namespace declarations on the Node class are applicable to all elements right? If I don't inherit from the Node class, the whole thing doesn't work anyway.

class Extensions extends Node
{
    /**
     * @JMS\XmlNamespace("https://fluend.nl/ns", prefix="fluend")
     * @JMS\Type("Fluend\Core\Bpmn\Deserialization\Attributes")
     * @var Attributes
     */
    public $attributes;
}

The above also produces null, unfortunately.

goetas commented 3 years ago

you need http://jmsyst.com/libs/serializer/master/reference/annotations#xmlelement

mijgame commented 3 years ago

Hi @goetas

I had already tried that:

  /**
     * @JMS\XmlAttribute(namespace="http://fluend.nl/ns")
     * @JMS\Type("Fluend\Core\Bpmn\Deserialization\Attributes")
     * @var Attributes
     */
    public $attributes;

and

  /**
     * @JMS\XmlAttribute
     * @JMS\Type("Fluend\Core\Bpmn\Deserialization\Attributes")
     * @var Attributes
     */
    public $attributes;

Both result in a null.

goetas commented 3 years ago

as you can see in the link ive posted, i mention @XmlElement(namespace="http://fluend.nl/ns"), not @JMS\XmlAttribute

mijgame commented 3 years ago

I'm sorry. The following does also produce a null:

   /**
     * @JMS\XmlElement(namespace="http://fluend.nl/ns")
     * @JMS\Type("Fluend\Core\Bpmn\Deserialization\Attributes")
     * @var Attributes
     */
    public $attributes;

and

   /**
     * @JMS\XmlElement(namespace="http://fluend.nl/ns", cdata=false)
     * @JMS\Type("Fluend\Core\Bpmn\Deserialization\Attributes")
     * @var Attributes
     */
    public $attributes;

Empty @XmlElement doesn't work either.

goetas commented 3 years ago

you should put @JMS\XmlElement(namespace="http://www.omg.org/spec/BPMN/20100524/MODEL") on the extensions node too.

I'm re-opening this issue so someone else can help you here, but I'm convinced that is not a bug, just a mis-configuration.

mijgame commented 3 years ago

Thank you for your help, I have figured it out. I ended up with the following: In Task:

    /**
     * @JMS\SerializedName("extensionElements")
     * @JMS\XmlElement(namespace="http://www.omg.org/spec/BPMN/20100524/MODEL")
     * @JMS\Type("Fluend\Core\Bpmn\Deserialization\Extensions")
     * @var Extensions
     */
    public $extensions;

In Extensions:

    /**
     * @JMS\XmlElement(namespace="http://fluend.nl/ns")
     * @JMS\Type("Fluend\Core\Bpmn\Deserialization\Attributes")
     * @var Attributes
     */
    public $attributes;

In Attributes (note the Namespace on the XmlList):

    /**
     * @JMS\Type("array<Fluend\Core\Bpmn\Deserialization\Mapping>")
     * @JMS\XmlList(inline=true,entry="mapping",skipWhenEmpty=false,namespace="http://fluend.nl/ns")
     * @var Mapping[]
     */
    public $mappings;