sabre-io / xml

sabre/xml is an XML library that you may not hate.
http://sabre.io/xml/
BSD 3-Clause "New" or "Revised" License
515 stars 77 forks source link

Deserialize unconsistent XML to a normalized Array #188

Open jzfgo opened 4 years ago

jzfgo commented 4 years ago

Hi,

I have this weird looking XML and I'm trying to convert it to a simple array.

So far I've managed to do most of it with keyValue and repeatingElements deserializers and a custom one for the <Child/> element.

My problem is with the <Context/> elements. Since they aren't wrapped in a parent element, I can't treat them like repeating elements and if I parse <Query/>’s children like key-value elements, I only get the first <Context/> element.

Any suggestions?

This an example of the XML that I'm trying to parse:

<Query>
    <Checkin>2020-08-01</Checkin>
    <Nights>4</Nights>
    <PropertyList>
        <Property>my-hotel</Property>
    </PropertyList>
    <DeadlineMs>500</DeadlineMs>
    <Context>
        <Occupancy>3</Occupancy>
        <UserCountry>CA</UserCountry>
        <UserDevice>tablet</UserDevice>
    </Context>
    <Context>
        <Occupancy>4</Occupancy>
        <OccupancyDetails>
            <NumAdults>2</NumAdults>
            <Children>
                <Child age="8"/>
                <Child age="5"/>
            </Children>
        </OccupancyDetails>
        <UserCountry>US</UserCountry>
        <UserDevice>mobile</UserDevice>
    </Context>
    <Context>
        <Occupancy>6</Occupancy>
        <OccupancyDetails>
            <NumAdults>4</NumAdults>
            <Children>
                <Child age="6"/>
                <Child age="10"/>
            </Children>
        </OccupancyDetails>
        <UserCountry>FR</UserCountry>
        <UserDevice>desktop</UserDevice>
    </Context>
</Query>

And this is the result that I would like to achieve:

[
    'Checkin' => '2020-08-01',
    'Nights' => 4,
    'PropertyList' => [
        'my-hotel',
    ],
    'DeadlineMs' => 500,
    'Contexts' => [
        [
            'Occupancy' => 3,
            'UserCountry' => 'CA',
            'UserDevice' => 'tablet',
        ],
        [
            'Occupancy' => 4,
            'OccupancyDetails' => [
                'NumAdults' => 2,
                'Children' => [
                    8,
                    5,
                ],
            ]
            'UserCountry' => 'US',
            'UserDevice' => 'mobile',
        ],
        [
            'Occupancy' => 6,
            'OccupancyDetails' => [
                'NumAdults' => 4,
                'Children' => [
                    6,
                    10,
                ],
            ]
            'UserCountry' => 'FR',
            'UserDevice' => 'desktop',
        ],
    ],
];

Best, Javier.

evert commented 4 years ago

To achieve this, you will also need a custom deserializer for Query, or if you are willing to switch from arrays to classes for Query, you could use the class mapper, which has a feature to deserialize properties to single properties, or properties for which there can be more than 1

jzfgo commented 4 years ago

Hi,

Thanks for your response.

I've tried making a custom deserializer for Query but what I need is to create a new node (Contexts) to wrap the Context elements and I don't know how to do that.

I thought about using classes too, but it looks like overcomplicating things to just parse a simple request.

Best, Javier.

jzfgo commented 4 years ago

Hi @evert,

As per your suggestion, I've switched to classes and, since I also intend write XML responses, I've decided to go all in and make a full blown library (its purpose is to handle updating pricing and availability in the Google Hotel Ads service):

https://github.com/bahiazul/google-hotel-ads-xml

However, I'm in a similar situation as before:

I've added an examples/ folder in the repo that I'm currently using for debugging. If you run php examples/query.php you can see the issues with the <Context> element when parsing and the <Property> and <Child> elements when writing.

For more info, all element classes inherit from a Base class that implements the xmlSerialize() and xmlDeserialize() methods.

There is also a Service class that inherits from the Sabre one where I'm defining all the mappings (VOs for simple elements and additional mappings for elements with attributes).

What am I doing wrong? I've read the docs and the source code from top to bottom and I still can't figure it out. Any help would be appreciated.

Best, Javier.

evert commented 4 years ago

I think in the case of $Context, the issue is that that property on the Query class is initialized as null. sabre/xml will try to figure out if something should be treated as an array by looking at it's value, and null implies to sabre/xml that it isn't.

It looks like that's an issue at at least a couple of places, so start there

jzfgo commented 4 years ago

Thanks @evert , I've made the modifications that you suggested but unfortunately I haven't seen any changes.

However, I've observed that, when turning off mapping to Query in Service, saber/xml does interpret multiple <Context> elements (as it should), which makes me think that the problem is with my custom deserializer.

Sadly this puts me in the same situation as before switching to classes. On the bright side, I now know where the problems exactly are. I just need to find how to fix them 😅.

Best, Javier.