Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: test

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
test:
name: Run tests on Node.js LTS
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Use Node.js LTS
uses: actions/setup-node@v4
with:
node-version: "lts/*"
cache: "npm"

- name: Install dependencies
run: npm ci

- name: Run tests
run: npm test
103 changes: 102 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,103 @@
# tlv-parser
A zero-dependency, recursive TLV (Tag-Length-Value) parser in pure ES Modules.

[![npm version](https://img.shields.io/npm/v/tlv-parser.svg)](https://www.npmjs.com/package/tlv-parser)
[![downloads](https://img.shields.io/npm/dm/tlv-parser.svg)](https://www.npmjs.com/package/tlv-parser)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE)
[![Build Status](https://github.com/bouroo/tlv-parser/actions/workflows/nodejs.yml/badge.svg)](https://github.com/bouroo/tlv-parser/actions)

Zero-dependency, recursive TLV (Tag-Length-Value) parser in pure ES Modules.
Supports both a raw array of `TLVNode` objects and a nested plain-object keyed by tag.

---

## Contents

- [Installation](#installation)
- [Usage](#usage)
- [parseTLV (object)](#parsetlv-object)
- [parseTLVNodes (raw nodes)](#parsetlvnodes-raw-nodes)
- [API](#api)
- [Project Structure](#project-structure)
- [Contributing](#contributing)
- [License](#license)

---

## Installation

```bash
# via npm
npm install tlv-parser

# or yarn
yarn add tlv-parser
```

---

## Usage

```js
import { parseTLV, parseTLVNodes } from 'tlv-parser';

// Sample TLV string
const tlv = '0046000600000101030140225202505252KGPGoQxQH5Z5RySO5102TH9104904A';

// 1) Get nested object keyed by tag
const obj = parseTLV(tlv);
console.log(obj);
/* {
"00": {
"00": "000001",
"01": "014",
"02": "202505252KGPGoQxQH5Z5RySO"
},
"51": "TH",
"91": "904A"
} */

// 2) Get raw TLVNode[]
const nodes = parseTLVNodes(tlv);
console.log(nodes);
/* [
TLVNode { tag: '00', length: 46, data: '', children: [ … ] },
TLVNode { tag: '51', length: 2, data: 'TH', children: [] },
TLVNode { tag: '91', length: 4, data: '904A', children: [] }
] */
```

---

## API

### parseTLV(tlvString: string): Record<string, any>

Parses a TLV-encoded string into a nested plain object keyed by tag.
Values are either strings (leaf data) or nested objects (sub-TLVs).

### parseTLVNodes(tlvString: string): TLVNode[]

Parses a TLV-encoded string into an array of `TLVNode` entities.
Each `TLVNode` has:
- `tag`: string (2-digit tag)
- `length`: number (parsed length)
- `data`: string (raw data if no children)
- `children`: `TLVNode[]` (empty if leaf)

---

## Contributing

1. Fork the repository
2. Create a branch (`git checkout -b feat/YourFeature`)
3. Commit your changes (`git commit -m "feat: add …"`)
4. Push to your branch (`git push origin feat/YourFeature`)
5. Open a Pull Request

Please follow [Conventional Commits](https://www.conventionalcommits.org/) and write tests.

---

## License

MIT © [Kawin Viriyaprasopsook](https://github.com/bouroo)
27 changes: 27 additions & 0 deletions example/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { parseTLV, parseTLVNodes } from "tlv-parser";

// Sample TLV string
const input =
"0046000600000101030140225202505252KGPGoQxQH5Z5RySO5102TH9104904A";

// 1) get nested object
const obj = parseTLV(input);
console.log(obj);
/*
{
"00": { "00": "000001", "01": "014", "02": "202505252KGPGoQxQH5Z5RySO" },
"51": "TH",
"91": "904A"
}
*/

// 2) get raw TLVNode[]
const nodes = parseTLVNodes(input);
console.log(nodes);
/*
[
TLVNode { tag: '00', length: 46, data: '', children: [ … ] },
TLVNode { tag: '51', length: 2, data: 'TH', children: [] },
TLVNode { tag: '91', length: 4, data: '904A', children: [] }
]
*/
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "tlv-parser",
"version": "1.0.0",
"description": "Zero-dependency recursive TLV (Tag-Length-Value) parser in pure ES modules. Supports raw TLVNode[] or nested object keyed by tag.",
"type": "module",
"main": "src/index.js",
"exports": {
".": {
"import": "./src/index.js"
}
},
"files": [
"src"
],
"scripts": {
"test": "node --test"
},
"repository": {
"type": "git",
"url": "git+https://github.com/bouroo/tlv-parser.git"
},
"keywords": [
"tlv",
"parser",
"tag-length-value",
"esmodule",
"clean-architecture",
"solid"
],
"author": "Kawin Viriyaprasopsook <kawin.v@kkumail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/bouroo/tlv-parser/issues"
},
"homepage": "https://github.com/bouroo/tlv-parser#readme"
}
41 changes: 41 additions & 0 deletions src/adapters/TLVParserAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { IParser } from "../interfaces/IParser.js";
import { TLVParser } from "../usecases/TLVParser.js";
import { TLVObjectifier } from "../usecases/TLVObjectifier.js";

export class TLVParserAdapter extends IParser {
/**
* @param {{ maxDepth?: number }} [options]
*/
constructor(options) {
super();
this.parser = new TLVParser(options);
this.objectifier = new TLVObjectifier();
}

/**
* @param {string} tlvString
* @returns {import('../domain/TLVNode.js').TLVNode[]}
*/
parseNodes(tlvString) {
if (typeof tlvString !== "string") {
throw new TypeError("parseNodes expects a string");
}
return this.parser.parse(tlvString);
}

/**
* @param {string} tlvString
* @returns {Record<string, any>}
*/
parseObject(tlvString) {
const nodes = this.parseNodes(tlvString);
return this.objectifier.objectify(nodes);
}

/**
* Default parse → object
*/
parse(tlvString) {
return this.parseObject(tlvString);
}
}
11 changes: 11 additions & 0 deletions src/domain/TLVNode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export class TLVNode {
constructor(tag, length, data = "", children = []) {
this.tag = tag;
this.length = length;
this.data = data;
this.children = children;
}
hasChildren() {
return this.children.length > 0;
}
}
21 changes: 21 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { TLVParserAdapter } from "./adapters/TLVParserAdapter.js";

const adapter = new TLVParserAdapter();

/**
* Parse TLV string → nested JS object keyed by tag
* @param {string} tlv
* @returns {Record<string, any>}
*/
export function parseTLV(tlv) {
return adapter.parseObject(tlv);
}

/**
* Parse TLV string → raw TLVNode[]
* @param {string} tlv
* @returns {import('./domain/TLVNode.js').TLVNode[]}
*/
export function parseTLVNodes(tlv) {
return adapter.parseNodes(tlv);
}
5 changes: 5 additions & 0 deletions src/interfaces/IParser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export class IParser {
parse(/* tlvString, options */) {
throw new Error("IParser.parse() must be implemented");
}
}
13 changes: 13 additions & 0 deletions src/usecases/TLVObjectifier.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export class TLVObjectifier {
objectify(nodes) {
const result = {};
for (const node of nodes) {
if (node.hasChildren()) {
result[node.tag] = this.objectify(node.children);
} else {
result[node.tag] = node.data;
}
}
return result;
}
}
Loading