xml icon indicating copy to clipboard operation
xml copied to clipboard

Deserialize unconsistent XML to a normalized Array

Open jzfgo opened this issue 5 years ago • 5 comments

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.

jzfgo avatar Jun 25 '20 08:06 jzfgo

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

evert avatar Jun 25 '20 15:06 evert

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 avatar Jun 29 '20 07:06 jzfgo

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:

  • When parsing repeating elements that are mixed with others at the same level I end up with only the last one of them
  • When writing repeating elements, I end up with just one that combining all their values or attributes.

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.

jzfgo avatar Aug 10 '20 11:08 jzfgo

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

evert avatar Aug 10 '20 20:08 evert

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.

jzfgo avatar Aug 11 '20 09:08 jzfgo