tally-clean-response
Lightweight, zero-dependency utility to clean, sanitize, and normalize JSON responses from Tally ERP 9 / TallyPrime XML-to-JSON parsers into clean, developer-friendly JavaScript objects.
Why is this needed? #
When you query Tally via XML/TDL (using libraries like tally-to-xml-tdl or fast-xml-parser), the parsed JSON contains heavy XML serialization artifacts:
- Typed wrappers: Scalar values are wrapped in type metadata objects, e.g.
{"@_TYPE": "String", "#text": "Pur Exp"}or{"@_TYPE": "Number", "#text": 1}instead of plain values. - Empty tag noise: Empty XML tags like
<ALLINVENTORYENTRIES.LIST TYPE="String"/>become{"@_TYPE": "String"}instead of clean empty values. - Deep nesting: Collection records are buried inside
ENVELOPE.BODY.DATA.COLLECTION.*. - Inconsistent single-item collections: Tally returns a single object instead of an array when only 1 record matches, causing random
.map is not a functionruntime bugs.
tally-clean-response solves all of this automatically in a single call, returning a normalized array of pristine JavaScript objects with full recursive unwrapping of all nested inventory and ledger lines.
Before vs After Comparison #
❌ Raw Parsed Tally XML Output (Before)
Every field is wrapped in #text and @_TYPE objects, empty nodes are dirty objects, and single rows are not arrays:
{
"ENVELOPE": {
"BODY": {
"DATA": {
"COLLECTION": {
"VOUCHER": [
{
"DATE": { "@_TYPE": "Date", "#text": "20260401" },
"GUID": "56b158ca-11af-4ad5-bc28-c1c2428041b9-0002a874",
"VOUCHERTYPENAME": { "@_TYPE": "String", "#text": "Pur Exp" },
"VOUCHERNUMBER": { "@_TYPE": "Number", "#text": 1 },
"ISDEEMEDPOSITIVE": { "@_TYPE": "Logical", "#text": "No" },
"ALLINVENTORYENTRIES.LIST": { "@_TYPE": "String" },
"@_REMOTEID": "56b158ca-11af-4ad5-bc28-c1c2428041b9-0002a874"
}
]
}
}
}
}
}
✅ Cleaned Output (After cleanTallyResponse)
A direct, clean JavaScript array with pure primitives, normalized empty strings, and preserved metadata:
[
{
"DATE": "20260401",
"GUID": "56b158ca-11af-4ad5-bc28-c1c2428041b9-0002a874",
"VOUCHERTYPENAME": "Pur Exp",
"VOUCHERNUMBER": 1,
"ISDEEMEDPOSITIVE": "No",
"ALLINVENTORYENTRIES.LIST": "",
"@_REMOTEID": "56b158ca-11af-4ad5-bc28-c1c2428041b9-0002a874"
}
]
Installation #
npm install tally-clean-response
Quick start #
1. Together with tally-to-xml-tdl
The standard way to fetch and immediately sanitize Tally data in Node.js:
import { vouchers } from "tally-to-xml-tdl";
import cleanTallyResponse from "tally-clean-response";
// 1. Fetch raw response from Tally Prime
const rawData = await vouchers.purchases.period("My Company", "1-Apr-2026", "30-Apr-2026");
// 2. Clean into a pristine array of vouchers
const cleanVouchers = cleanTallyResponse(rawData);
console.log(`Cleaned ${cleanVouchers.length} vouchers:`);
console.log(cleanVouchers[0]);
2. Usage with fast-xml-parser
If you query Tally Prime using raw HTTP and fast-xml-parser:
import { XMLParser } from "fast-xml-parser";
import cleanTallyResponse from "tally-clean-response";
const parser = new XMLParser({
ignoreAttributes: false,
attributeNamePrefix: "@_",
textNodeName: "#text"
});
const rawJson = parser.parse(tallyXmlResponseString);
const cleanedRecords = cleanTallyResponse(rawJson);
Type Conversion Matrix #
When unwrapping leaf nodes with @_TYPE:
| Tally @_TYPE | Sample Raw Input | Cleaned Output | Result Type |
|---|---|---|---|
| String | {"@_TYPE": "String", "#text": "Sales"} |
"Sales" |
string |
| Date | {"@_TYPE": "Date", "#text": "20260401"} |
"20260401" |
string |
| Logical | {"@_TYPE": "Logical", "#text": "Yes"} |
"Yes" |
string |
| Rate | {"@_TYPE": "Rate", "#text": "500/Nos"} |
"500/Nos" |
string |
| Number | {"@_TYPE": "Number", "#text": 125} |
125 |
number |
| Amount | {"@_TYPE": "Amount", "#text": 15000.5} |
15000.5 |
number / string |
| Quantity | {"@_TYPE": "Quantity", "#text": 10} |
10 |
number / string |
| Empty tag | {"@_TYPE": "String"} (no #text) |
"" |
string |
Attributes prefixed with @_ (such as @_REMOTEID, @_VCHKEY, @_VCHTYPE) attached directly to row objects are preserved intact for indexing and joins.
TypeScript Support #
Full TypeScript definitions are included out of the box with generic type parameter support:
import cleanTallyResponse from "tally-clean-response";
interface VoucherRow {
DATE: string;
GUID: string;
VOUCHERTYPENAME: string;
VOUCHERNUMBER: number;
[key: string]: any;
}
const cleaned = cleanTallyResponse<VoucherRow>(tallyRawResponse);
// cleaned has type VoucherRow[]