unfuxml
v2.0.0
Published
Un-f*ck any XML with a one-way JSON conversion. Customizable, including smart defaults: flattens excess nesting, de-dupes, de-namespaces, and preserve arrays.
Downloads
1
Maintainers
Readme
unfuxml 🛠✨
Why?
Unfuxml is the best way to get familiar JSON from XML.
The Problem
Most XML to JSON conversion libraries produce JSON which is less-than-clean, difficult-to-traverse, and verbose.
Hint: Hit the Play icon on the animation below:
Solution
An XML to JSON converter with configurable (practical) assumptions. And "escape hatches" where appropriate.
Unfuxml
features:
- [x] One-way conversion.
- [x] De-nesting of XML's excess nesting.
- [ ] Key/Node name transformation.
- [x] Camel-cases by default.
- [x] Key rewriting function.
- [ ] Removes namespace prefixes, customize the
keyNameFunction
option to override. - [ ] Configurable.
- [ ] Supports dictionary for simple remapping of poorly named source data.
- [ ] Included stats helper functions. Try
import {getXmlToJsonStats} from 'unfuxml';
- (Test out your XML using
repl.it
orrunkit.com/new
)
Usage
- Default
unfuxml()
function. - Helper
getXmlToJsonStats
method.
unfuxml(xml, options)
Example #1
import lodash from 'lodash';
import unfuxml from 'unfuxml';
const xmlString = `<?xml version="1.0" encoding="UTF-8"?>
<StateList>
<State name="CA" />
<State name="CO" />
<State name="WA" />
</StateList>`;
const results = unfuxml(xmlString);
console.log(results);
/*
{
stateList: [
{ name: 'CA' },
{ name: 'CO' },
{ name: 'WA' },
],
}
*/
Example #2
Ensure certain named nodes containing a single-Element can be treated as an array.
Enabled by the
alwaysArray: ['StateList']
option.
import lodash from 'lodash';
import unfuxml from 'unfuxml';
const xmlString = `<?xml version="1.0" encoding="UTF-8"?>
<StateList>
<State name="CA" />
</StateList>`;
console.log(unfuxml(xmlString, {
alwaysArray: ['StateList'],
}));
/*
{ stateList: [ { name: 'CA' } ] }
*/
// Without the `alwaysArray` option, single-Element
// containing nodes are treated as singular objects.
console.log(unfuxml(xmlString, {
alwaysArray: false, // Disable by default.
}));
/*
{ stateList: { name: 'CA' } }
*/
Example #3 (w/ Options)
import lodash from 'lodash';
import unfuxml from 'unfuxml';
import {promises as fs} from 'fs';
(async () => {
const xmlString = await fs.readFile(path.resolve('./input.xml'), 'utf8');
const results = unfuxml(xmlString, {
alwaysArray: ['StateList'],
unwrapLists: false,
keyNameFunction: (keyName: string) => lodash.snakeCase(keyName),
});
console.log(results);
/*
{
stateList: [
{ name: 'CA' },
{ name: 'CO' },
{ name: 'WA' },
],
}
*/
})();
Input & Output Samples
Sample: Hotel Query
<?xml version="1.0" encoding="UTF-8"?>
<Transaction timestamp="2020-07-23T16:20:00-04:00" id="42">
<Result>
<Property>1234</Property>
<Checkin>2021-01-13</Checkin>
<Nights>9</Nights>
<Baserate currency="USD">3196.1</Baserate>
<Tax currency="USD">559.49</Tax>
<OtherFees currency="USD">543.34</OtherFees>
<Occupancy>2</Occupancy>
<Rates>
<Rate>
<Baserate currency="USD">3196.1</Baserate>
<Tax currency="USD">559.49</Tax>
<OtherFees currency="USD">543.34</OtherFees>
<Occupancy>1</Occupancy>
</Rate>
<Rate>
<Baserate currency="USD">3196.1</Baserate>
<Tax currency="USD">559.49</Tax>
<OtherFees currency="USD">543.34</OtherFees>
<Occupancy>3</Occupancy>
</Rate>
<Rate>
<Baserate currency="USD">3196.1</Baserate>
<Tax currency="USD">559.49</Tax>
<OtherFees currency="USD">543.34</OtherFees>
<Occupancy>4</Occupancy>
</Rate>
<Rate>
<Baserate currency="USD">3196.1</Baserate>
<Tax currency="USD">559.49</Tax>
<OtherFees currency="USD">543.34</OtherFees>
<Occupancy>5</Occupancy>
</Rate>
<Rate>
<Baserate currency="USD">3196.1</Baserate>
<Tax currency="USD">559.49</Tax>
<OtherFees currency="USD">543.34</OtherFees>
<Occupancy>6</Occupancy>
</Rate>
</Rates>
</Result>
</Transaction>
{
"transaction": {
"id": "42",
"timestamp": "2020-07-23T16:20:00-04:00",
"result": {
"property": 1234,
"checkin": "2021-01-13",
"nights": 9,
"baserate": { "currency": "USD", "value": 3196.1 },
"tax": { "currency": "USD", "value": 559.49 },
"otherFees": { "currency": "USD", "value": 543.34 },
"occupancy": 2,
"rates": {
"rate": [
{
"baserate": { "currency": "USD", "value": 3196.1 },
"otherFees": { "currency": "USD", "value": 543.34 },
"tax": { "currency": "USD", "value": 559.49 },
"occupancy": 1
},
{
"baserate": { "currency": "USD", "value": 3196.1 },
"otherFees": { "currency": "USD", "value": 543.34 },
"tax": { "currency": "USD", "value": 559.49 },
"occupancy": 3
},
{
"baserate": { "currency": "USD", "value": 3196.1 },
"otherFees": { "currency": "USD", "value": 543.34 },
"tax": { "currency": "USD", "value": 559.49 },
"occupancy": 4
},
{
"baserate": { "currency": "USD", "value": 3196.1 },
"otherFees": { "currency": "USD", "value": 543.34 },
"tax": { "currency": "USD", "value": 559.49 },
"occupancy": 5
},
{
"baserate": { "currency": "USD", "value": 3196.1 },
"otherFees": { "currency": "USD", "value": 543.34 },
"tax": { "currency": "USD", "value": 559.49 },
"occupancy": 6
}
]
}
}
}
}
Sample: Hotel Property Data Set
<?xml version="1.0" encoding="UTF-8"?>
<Transaction timestamp="2017-07-18T16:20:00-04:00" id="42">
<!-- A transaction message with room types result. -->
<PropertyDataSet>
<Property>12345</Property>
<RoomData>
<RoomID>single</RoomID>
<Name>
<Text text="Single room" language="en"/>
<Text text="Chambre simple" language="fr"/>
</Name>
<Description>
<Text text="A single room" language="en"/>
<Text text="Le chambre simple" language="fr"/>
</Description>
<PhotoURL>
<Caption>
<Text text="Living area" language="en"/>
<Text text="Le chambre" language="fr"/>
</Caption>
<URL>http://www.foo.com/static/bar/image1234.jpg</URL>
</PhotoURL>
<PhotoURL>
<URL>http://www.foo.com/static/bar/image1235.jpg</URL>
</PhotoURL>
<Capacity>2</Capacity>
</RoomData>
<RoomData>
<RoomID>double</RoomID>
<Name>
<Text text="Double room" language="en"/>
<Text text="Chambre double" language="fr"/>
</Name>
<Occupancy>1</Occupancy>
</RoomData>
<PackageData>
<PackageID>refundbreakfast</PackageID>
<Name>
<Text
text="Refundable Room with Breakfast"
language="en"
/>
<Text
text="Chambre remboursable avec le petit déjeuner"
language="fr"
/>
</Name>
<Description>
<Text text="Continental Breakfast" language="en"/>
<Text text="Petit déjeuner continental" language="fr"/>
</Description>
<ChargeCurrency>hotel</ChargeCurrency>
<Refundable available="1" refundable_until_days="3"/>
<BreakfastIncluded>1</BreakfastIncluded>
</PackageData>
<PackageData>
<PackageID>prepaid</PackageID>
<Name>
<Text text="Nonrefundable" language="en"/>
<Text text="Non remboursable" language="fr"/>
</Name>
<Description>
<Text text="Blah blah blad" language="en"/>
<Text text="Le blah blah blad" language="fr"/>
</Description>
<Occupancy>2</Occupancy>
<ChargeCurrency>web</ChargeCurrency>
<Refundable available="0"/>
</PackageData>
</PropertyDataSet>
</Transaction>
{
"transaction": {
"id": "42",
"timestamp": "2017-07-18T16:20:00-04:00",
"propertyDataSet": {
"property": 12345,
"roomData": [
{
"roomId": "single",
"name": {
"text": [
{ "text": "Single room", "language": "en" },
{ "text": "Chambre simple", "language": "fr" }
]
},
"description": {
"text": [
{ "text": "A single room", "language": "en" },
{ "text": "Le chambre simple", "language": "fr" }
]
},
"photoUrl": [
{
"url": "http://www.foo.com/static/bar/image1234.jpg",
"caption": {
"text": [
{ "text": "Living area", "language": "en" },
{ "text": "Le chambre", "language": "fr" }
]
}
},
{
"url": "http://www.foo.com/static/bar/image1235.jpg"
}
],
"capacity": 2
},
{
"roomId": "double",
"name": {
"text": [
{ "text": "Double room", "language": "en" },
{ "text": "Chambre double", "language": "fr" }
]
},
"occupancy": 1
}
],
"packageData": [
{
"packageId": "refundbreakfast",
"name": {
"text": [
{ "text": "Refundable Room with Breakfast", "language": "en" },
{ "text": "Chambre remboursable avec le petit déjeuner", "language": "fr" }
]
},
"description": {
"text": [
{ "text": "Continental Breakfast", "language": "en" },
{ "text": "Petit déjeuner continental", "language": "fr" }
]
},
"chargeCurrency": "hotel",
"refundable": { "available": "1", "refundableUntilDays": "3" },
"breakfastIncluded": 1
},
{
"packageId": "prepaid",
"name": {
"text": [
{ "text": "Nonrefundable", "language": "en" },
{ "text": "Non remboursable", "language": "fr" }
]
},
"description": {
"text": [
{ "text": "Blah blah blad", "language": "en" },
{ "text": "Le blah blah blad", "language": "fr" }
]
},
"occupancy": 2,
"chargeCurrency": "web",
"refundable": { "available": "0" } }
]
}
}
}
Sample: Hotel Rate Modification
<?xml version="1.0" encoding="UTF-8"?>
<RateModifications
id="B78BA3ED-31C5-44D7-80F7-69E12AEAA1BD"
partner="partner_key"
timestamp="timestamp">
<HotelRateModifications hotel_id="123" action="overlay">
<ItineraryRateModification id="35725" action="delete">
<BookingDates>
<DateRange
start="2030-04-15"
end="2030-04-15"
days_of_week="M" />
<DateRange
start="2030-04-15"
end="2030-04-15"
days_of_week="M" />
</BookingDates>
<BookingWindow min="integer" max="integer" />
<CheckinDates>
<DateRange
start="2030-04-15"
end="2030-04-15"
days_of_week="M" />
</CheckinDates>
<CheckoutDates>
<DateRange
start="2030-04-15"
end="2030-04-15"
days_of_week="M" />
</CheckoutDates>
<Devices>
<Device type="desktop" />
</Devices>
<LengthOfStay min="integer" max="integer" />
<MinimumAmount before_discount="integer" />
<RatePlans>
<RatePlan id="PackageID_1" />
<RatePlan id="PackageID_2" />
</RatePlans>
<RoomTypes>
<RoomType id="RoomID_1" />
<RoomType id="RoomID_2" />
</RoomTypes>
<StayDates application="all">
<DateRange
start="2030-04-15"
end="2030-04-15"
days_of_week="M" />
</StayDates>
<UserCountries type="include">
<Country code="USA" />
</UserCountries>
<ModificationActions>
<PriceAdjustment multiplier="float" />
<RateRule id="9876" />
<Refundable
available="false"
refundable_until_days="1"
refundable_until_time="time" />
<Availability status="unavailable" />
</ModificationActions>
</ItineraryRateModification>
</HotelRateModifications>
</RateModifications>
{
"rateModifications": {
"id": "B78BA3ED-31C5-44D7-80F7-69E12AEAA1BD",
"partner": "partner_key",
"timestamp": "timestamp",
"hotelRateModifications": {
"hotelId": "hotel-id-123-abc",
"action": "overlay",
"itineraryRateModification": {
"id": "357238795",
"action": "delete",
"bookingDates": {
"dateRange": [
{ "start": "2030-04-15", "end": "2030-04-15", "daysOfWeek": "M" },
{ "start": "2030-04-15", "end": "2030-04-15", "daysOfWeek": "M" }
]
},
"bookingWindow": { "min": "integer", "max": "integer" },
"checkinDates": {
"dateRange": { "start": "2030-04-15", "end": "2030-04-15", "daysOfWeek": "M" }
},
"checkoutDates": {
"dateRange": { "start": "2030-04-15", "end": "2030-04-15", "daysOfWeek": "M" }
},
"devices": { "device": { "type": "desktop" } },
"lengthOfStay": { "min": "integer", "max": "integer" },
"minimumAmount": { "beforeDiscount": "integer" },
"ratePlans": { "ratePlan": [ { "id": "PackageID_1" }, { "id": "PackageID_2" }] },
"roomTypes": { "roomType": [ { "id": "RoomID_1" }, { "id": "RoomID_2" }] },
"stayDates": {
"dateRange": { "start": "2030-04-15", "end": "2030-04-15", "daysOfWeek": "M" },
"application": "all"
},
"userCountries": { "country": { "code": "USA" }, "type": "include" },
"modificationActions": {
"priceAdjustment": { "multiplier": "float" },
"rateRule": { "id": "9876" },
"refundable": {
"available": "false",
"refundableUntilDays": "1",
"refundableUntilTime": "time"
},
"availability": { "status": "unavailable" }
}
}
}
}
}
TODO
[x] ~~~Support De-nesting Common Object-Array Patterns~~~
// ❌ Instead of this:
{ "propertyDataSet": { "propertyData": [ {} ] } }
// ✅ Should instead collapse to a single set/array
{ "propertyDataSet": [ {} ] }
FAQ
Xml Hell
Potentially Lossy Situations
- Representing: Multiple CDATA siblings co-mingled with Nodes in a Node List.
- Heavy reliance on namespaces. AKA Tag Name collisions when ignoring prefixes (the bit before the
:
.)