The suppliers still speak XML
A large share of the supplier APIs behind our gateway are SOAP/XML, sitting alongside the JSON ones as if REST had never happened. You do not get to choose; the supplier's contract is the supplier's contract.
And the responses are not small. Our integration docs name two offer patterns: pattern A, where the supplier returns a single token that books N rooms, and pattern B, where each room's rate is independent and the offer list is the materialised matrix of every valid room-rate combination. Under pattern B, one search response can carry thousands of rate combinations. Parsing stops being a footnote and becomes a bulk operation that runs on every search.
Two ways to read it
The obvious way is a DOM walk: parse the whole document into a tree in memory, then walk the tree with hand-written code that plucks values and assembles JSON. At small size this is fine, and every tutorial does it. At thousands of combinations you are holding two copies of everything at once — the full DOM and the objects you are building from it — inside a worker that is doing this for many suppliers in parallel.
The way we chose is a declarative mapping: camaro for reading responses, xmlbuilder for writing requests. With camaro the mapping is a template — this JSON field comes from this path, this array comes from these nodes — and the library does the traversal. The transform you maintain is data, not code.
I will be honest about what was not measured: no before/after memory number was recorded for this choice, so I cannot tell you what the DOM walk would have cost here. The one place on this site where a real memory measurement exists is the Redis-memory lesson, and this is not it. The argument for the template stands on other legs.
What makes supplier XML hard, beyond size
Size is the least interesting problem. Three things bite harder.
Optional nodes. A cancellation-policy element that exists on some offers and not others. A DOM walk that reaches through it crashes on the response that omits it — usually in production, on the one supplier that omits it.
One element where you expected an array. XML has no arrays. Two sibling rooms look like a list; one room looks like a single object. A walk written against the two-room example breaks on the one-room response, and the one-room response is exactly the case your test data never had. A template lets you declare "this is always an array" once, and the shape stops depending on the count.
Namespaces that differ per supplier. The same logical document arrives with different prefixes from different suppliers, and sometimes different namespace choices between endpoints of the same supplier. In a walk, that knowledge is smeared through every node access. In a template it is declared at the top, once.
The review argument
The quiet win is none of the above. A mapping expressed as a template is reviewable by someone who has never read the parser. In a merge request, the template reads like a data dictionary: field, path, type. A reviewer can hold the supplier's sample response in one window and the template in the other and check them line against line. A DOM walk can only be debugged — you cannot verify it by reading it, you can only run it and see.
With around 90 distinct integrations behind roughly 150 supplier codes, new mappings and fixes to old ones are routine work, done by whoever picks up the ticket. The template is what lets that be routine.
The rule
Below a certain volume, parser choice is taste. Above it, it is architecture: memory shape, failure shape, and — the part that compounds — review shape. Prefer the representation of the transform that a colleague can check without executing it. The template is slower to write the first time. It is faster every time after that, and it is the only version of the transform that a code review can actually read.