If you're into C++, take a look at benejson's pull parser. There is a C core but its presently very ugly and only intended for library, not application developers.
Often you can avoid almost any memory overhead. E.g. any structures that are allocated on opening a document, and deallocated on closure, can be allocated from larger buffers without keeping any information about the individual allocations. That can save anything up to 16-20 bytes per allocation with many malloc() implementations, and reduces typical allocation cost to incrementing a pointer and checking whether or not you need to allocate a new buffer (and the allocation cost for new buffers might be amortised over anything up to thousands of small allocations).
For a practical example, there's a font library that calls malloc() to allocate structures for every single glyph when opening a font. Most of the (thousands) of allocations are 4-8 bytes. Changing that to using an arena for an application I did, cut memory usage per font to about 25%-30% and cut load time for fonts to <10% by avoiding the malloc() calls.
You can achieve the same by throwing abstractions out the window and putting stuff in arrays etc. But arenas is often a very effective way of keeping the abstractions while effectively getting almost the same performance and memory usage.