├── .github └── FUNDING.yml ├── .gitignore ├── CONTRIBUTING.md ├── DOCUMENTATION.md ├── LICENSE ├── README.md ├── dist └── json2md.min.js ├── example └── index.js ├── lib ├── converters.js └── index.js ├── package.json └── test └── index.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ionicabizau 2 | patreon: ionicabizau 3 | open_collective: ionicabizau 4 | custom: https://www.buymeacoffee.com/h96wwchmy -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *~ 4 | *.log 5 | node_modules 6 | *.env 7 | .DS_Store 8 | package-lock.json 9 | .bloggify/* 10 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # 🌟 Contributing 2 | 3 | Want to contribute to this project? Great! Please read these quick steps to streamline the process and avoid unnecessary tasks. ✨ 4 | 5 | ## 💬 Discuss Changes 6 | Start by opening an issue in the repository using the [bug tracker][1]. Describe your proposed contribution or the bug you've found. If relevant, include platform info and screenshots. 🖼️ 7 | 8 | Wait for feedback before proceeding unless the fix is straightforward, like a typo. 📝 9 | 10 | ## 🔧 Fixing Issues 11 | 12 | Fork the project and create a branch for your fix, naming it `some-great-feature` or `some-issue-fix`. Commit changes while following the [code style][2]. If the project has tests, add one. ✅ 13 | 14 | If a `package.json` or `bower.json` exists, add yourself to the `contributors` array; create it if it doesn't. 🙌 15 | 16 | ```json 17 | { 18 | "contributors": [ 19 | "Your Name (http://your.website)" 20 | ] 21 | } 22 | ``` 23 | 24 | ## 📬 Creating a Pull Request 25 | Open a pull request and reference the initial issue (e.g., *fixes #*). Provide a clear title and consider adding visual aids for clarity. 📊 26 | 27 | ## ⏳ Wait for Feedback 28 | Your contributions will be reviewed. If feedback is given, update your branch as needed, and the pull request will auto-update. 🔄 29 | 30 | ## 🎉 Everyone Is Happy! 31 | Your contributions will be merged, and everyone will appreciate your effort! 😄❤️ 32 | 33 | Thanks! 🤩 34 | 35 | [1]: /issues 36 | [2]: https://github.com/IonicaBizau/code-style -------------------------------------------------------------------------------- /DOCUMENTATION.md: -------------------------------------------------------------------------------- 1 | ## Documentation 2 | 3 | You can see below the API reference of this module. 4 | 5 | ### `json2md(data, prefix)` 6 | Converts a JSON input to markdown. 7 | 8 | **Supported elements** 9 | 10 | | Type | Element | Data | Example | 11 | |--------------|--------------------|--------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------| 12 | | `h1` | Heading 1 | The heading text as string. | `{ h1: "heading 1" }` | 13 | | `h2` | Heading 2 | The heading text as string. | `{ h2: "heading 2" }` | 14 | | `h3` | Heading 3 | The heading text as string. | `{ h3: "heading 3" }` | 15 | | `h4` | Heading 4 | The heading text as string. | `{ h4: "heading 4" }` | 16 | | `h5` | Heading 5 | The heading text as string. | `{ h5: "heading 5" }` | 17 | | `h6` | Heading 6 | The heading text as string. | `{ h6: "heading 6" }` | 18 | | `p` | Paragraphs | The paragraph text as string or array (multiple paragraphs). | `{ p: "Hello World"}` or multiple paragraphs: `{ p: ["Hello", "World"] }` | 19 | | `blockquote` | Blockquote | The blockquote as string or array (multiple blockquotes) | `{ blockquote: "Hello World"}` or multiple blockquotes: `{ blockquote: ["Hello", "World"] }` | 20 | | `img` | Image | An object or an array of objects containing the `title`, `source` and `alt` fields. | `{ img: { title: "My image title", source: "http://example.com/image.png", alt: "My image alt" } }` | 21 | | `ul` | Unordered list | An array of strings or lists representing the items. | `{ ul: ["item 1", "item 2"] }` | 22 | | `ol` | Ordered list | An array of strings or lists representing the items. | `{ ol: ["item 1", "item 2"] }` | 23 | | `hr` | Separator | None | `{ hr: "" }` | 24 | | `code` | Code block element | An object containing the `language` (`String`) and `content` (`Array` or `String`) fields. | `{ code: { "language": "html", "content": "" } }` | 25 | | `table` | Table | An object containing the `headers` (`Array` of `String`s) and `rows` (`Array` of `Array`s or `Object`s). | `{ table: { headers: ["a", "b"], rows: [{ a: "col1", b: "col2" }] } }` or `{ table: { headers: ["a", "b"], rows: [["col1", "col2"]] } }` | 26 | | `link` | Link | An object containing the `title` and the `source` fields. | `{ title: 'hello', source: 'https://ionicabizau.net' }` | 27 | 28 | You can extend the `json2md.converters` object to support your custom types. 29 | 30 | ```js 31 | json2md.converters.sayHello = function (input, json2md) { 32 | return "Hello " + input + "!" 33 | } 34 | ``` 35 | 36 | Then you can use it: 37 | 38 | ```js 39 | json2md({ sayHello: "World" }) 40 | // => "Hello World!" 41 | ``` 42 | 43 | #### Params 44 | 45 | - **Array|Object|String** `data`: The input JSON data. 46 | - **String** `prefix`: A snippet to add before each line. 47 | 48 | #### Return 49 | - **String** The generated markdown result. 50 | 51 | ### async 52 | 53 | #### Params 54 | 55 | - **Array|Object|String** `data`: The input JSON data. 56 | - **String** `prefix`: A snippet to add before each line. 57 | 58 | #### Return 59 | - **Promise.\** The generated markdown result. 60 | 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-25 Ionică Bizău (https://ionicabizau.net) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | [![json2md](http://i.imgur.com/uj64JFw.png)](#) 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | # json2md 23 | 24 | [![Support me on Patreon][badge_patreon]][patreon] [![Buy me a book][badge_amazon]][amazon] [![PayPal][badge_paypal_donate]][paypal-donations] [![Ask me anything](https://img.shields.io/badge/ask%20me-anything-1abc9c.svg)](https://github.com/IonicaBizau/ama) [![Version](https://img.shields.io/npm/v/json2md.svg)](https://www.npmjs.com/package/json2md) [![Downloads](https://img.shields.io/npm/dt/json2md.svg)](https://www.npmjs.com/package/json2md) [![Get help on Codementor](https://cdn.codementor.io/badges/get_help_github.svg)](https://www.codementor.io/@johnnyb?utm_source=github&utm_medium=button&utm_term=johnnyb&utm_campaign=github) 25 | 26 | Buy Me A Coffee 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | > A JSON to Markdown converter. 35 | 36 | 37 | 38 | 39 | 40 | 41 | If you're looking to use this on the client side, that's also possible. Check out the [`dist`](/dist) directory. 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | ## :cloud: Installation 55 | 56 | ```sh 57 | # Using npm 58 | npm install --save json2md 59 | 60 | # Using yarn 61 | yarn add json2md 62 | ``` 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | ## :clipboard: Example 77 | 78 | 79 | 80 | ```js 81 | const json2md = require("json2md") 82 | 83 | console.log(json2md([ 84 | { h1: "JSON To Markdown" } 85 | , { blockquote: "A JSON to Markdown converter." } 86 | , { img: [ 87 | { title: "Some image", source: "https://example.com/some-image.png" } 88 | , { title: "Another image", source: "https://example.com/some-image1.png" } 89 | , { title: "Yet another image", source: "https://example.com/some-image2.png" } 90 | ] 91 | } 92 | , { h2: "Features" } 93 | , { ul: [ 94 | "Easy to use" 95 | , "You can programmatically generate Markdown content" 96 | , "..." 97 | ] 98 | } 99 | , { h2: "How to contribute" } 100 | , { ol: [ 101 | "Fork the project" 102 | , "Create your branch" 103 | , "Raise a pull request" 104 | ] 105 | } 106 | , { h2: "Code blocks" } 107 | , { p: "Below you can see a code block example." } 108 | , { "code": { 109 | language: "js" 110 | , content: [ 111 | "function sum (a, b) {" 112 | , " return a + b" 113 | , "}" 114 | , "sum(1, 2)" 115 | ] 116 | } 117 | } 118 | ])) 119 | // => 120 | // # JSON To Markdown 121 | // > A JSON to Markdown converter. 122 | // 123 | // ![Some image](https://example.com/some-image.png) 124 | // 125 | // ![Another image](https://example.com/some-image1.png) 126 | // 127 | // ![Yet another image](https://example.com/some-image2.png) 128 | // 129 | // ## Features 130 | // 131 | // - Easy to use 132 | // - You can programmatically generate Markdown content 133 | // - ... 134 | // 135 | // ## How to contribute 136 | // 137 | // 1. Fork the project 138 | // 2. Create your branch 139 | // 3. Raise a pull request 140 | // 141 | // ## Code blocks 142 | // 143 | // Below you can see a code block example. 144 | // 145 | // ```js 146 | // function sum (a, b) { 147 | // return a + b 148 | // } 149 | // sum(1, 2) 150 | // ``` 151 | ``` 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | ## :memo: Documentation 164 | 165 | 166 | ### `json2md(data, prefix)` 167 | Converts a JSON input to markdown. 168 | 169 | **Supported elements** 170 | 171 | | Type | Element | Data | Example | 172 | |--------------|--------------------|--------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------| 173 | | `h1` | Heading 1 | The heading text as string. | `{ h1: "heading 1" }` | 174 | | `h2` | Heading 2 | The heading text as string. | `{ h2: "heading 2" }` | 175 | | `h3` | Heading 3 | The heading text as string. | `{ h3: "heading 3" }` | 176 | | `h4` | Heading 4 | The heading text as string. | `{ h4: "heading 4" }` | 177 | | `h5` | Heading 5 | The heading text as string. | `{ h5: "heading 5" }` | 178 | | `h6` | Heading 6 | The heading text as string. | `{ h6: "heading 6" }` | 179 | | `p` | Paragraphs | The paragraph text as string or array (multiple paragraphs). | `{ p: "Hello World"}` or multiple paragraphs: `{ p: ["Hello", "World"] }` | 180 | | `blockquote` | Blockquote | The blockquote as string or array (multiple blockquotes) | `{ blockquote: "Hello World"}` or multiple blockquotes: `{ blockquote: ["Hello", "World"] }` | 181 | | `img` | Image | An object or an array of objects containing the `title`, `source` and `alt` fields. | `{ img: { title: "My image title", source: "http://example.com/image.png", alt: "My image alt" } }` | 182 | | `ul` | Unordered list | An array of strings or lists representing the items. | `{ ul: ["item 1", "item 2"] }` | 183 | | `ol` | Ordered list | An array of strings or lists representing the items. | `{ ol: ["item 1", "item 2"] }` | 184 | | `hr` | Separator | None | `{ hr: "" }` | 185 | | `code` | Code block element | An object containing the `language` (`String`) and `content` (`Array` or `String`) fields. | `{ code: { "language": "html", "content": "" } }` | 186 | | `table` | Table | An object containing the `headers` (`Array` of `String`s) and `rows` (`Array` of `Array`s or `Object`s). | `{ table: { headers: ["a", "b"], rows: [{ a: "col1", b: "col2" }] } }` or `{ table: { headers: ["a", "b"], rows: [["col1", "col2"]] } }` | 187 | | `link` | Link | An object containing the `title` and the `source` fields. | `{ title: 'hello', source: 'https://ionicabizau.net' }` | 188 | 189 | You can extend the `json2md.converters` object to support your custom types. 190 | 191 | ```js 192 | json2md.converters.sayHello = function (input, json2md) { 193 | return "Hello " + input + "!" 194 | } 195 | ``` 196 | 197 | Then you can use it: 198 | 199 | ```js 200 | json2md({ sayHello: "World" }) 201 | // => "Hello World!" 202 | ``` 203 | 204 | #### Params 205 | 206 | - **Array|Object|String** `data`: The input JSON data. 207 | - **String** `prefix`: A snippet to add before each line. 208 | 209 | #### Return 210 | - **String** The generated markdown result. 211 | 212 | ### async 213 | 214 | #### Params 215 | 216 | - **Array|Object|String** `data`: The input JSON data. 217 | - **String** `prefix`: A snippet to add before each line. 218 | 219 | #### Return 220 | - **Promise.\** The generated markdown result. 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | ## :question: Get Help 231 | 232 | There are few ways to get help: 233 | 234 | 235 | 236 | 1. Please [post questions on Stack Overflow](https://stackoverflow.com/questions/ask). You can open issues with questions, as long you add a link to your Stack Overflow question. 237 | 2. For bug reports and feature requests, open issues. :bug: 238 | 3. For direct and quick help, you can [use Codementor](https://www.codementor.io/johnnyb). :rocket: 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | ## :yum: How to contribute 254 | Have an idea? Found a bug? See [how to contribute][contributing]. 255 | 256 | 257 | ## :sparkling_heart: Support my projects 258 | I open-source almost everything I can, and I try to reply to everyone needing help using these projects. Obviously, 259 | this takes time. You can integrate and use these projects in your applications *for free*! You can even change the source code and redistribute (even resell it). 260 | 261 | However, if you get some profit from this or just want to encourage me to continue creating stuff, there are few ways you can do it: 262 | 263 | 264 | - Starring and sharing the projects you like :rocket: 265 | - [![Buy me a book][badge_amazon]][amazon]—I love books! I will remember you after years if you buy me one. :grin: :book: 266 | - [![PayPal][badge_paypal]][paypal-donations]—You can make one-time donations via PayPal. I'll probably buy a ~~coffee~~ tea. :tea: 267 | - [![Support me on Patreon][badge_patreon]][patreon]—Set up a recurring monthly donation and you will get interesting news about what I'm doing (things that I don't share with everyone). 268 | - **Bitcoin**—You can send me bitcoins at this address (or scanning the code below): `1P9BRsmazNQcuyTxEqveUsnf5CERdq35V6` 269 | 270 | ![](https://i.imgur.com/z6OQI95.png) 271 | 272 | 273 | Thanks! :heart: 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | ## :dizzy: Where is this library used? 291 | If you are using this library in one of your projects, add it in this list. :sparkles: 292 | 293 | - `@aligov/module-doc` 294 | - `@apica-io/asm-auto-deploy` 295 | - `@asyncapi/diff` 296 | - `@balmacefa/function_tool_kit` 297 | - `@best/github-integration` 298 | - `@best/store` 299 | - `@bonitasoft/dependency-list-to-markdown` 300 | - `@bwagener/gridsome-source-google-docs` 301 | - `@cloudcatalog/cli` 302 | - `@cloudcatalog/cli-test` 303 | - `@cobalt-engine/change-logger` 304 | - `@cypress/schema-tools` 305 | - `@dididc/dc-extension` 306 | - `@dlsl/hardhat-markup` 307 | - `@dschau/gatsby-source-google-docs` 308 | - `@e2y/bdd-dictionary-generator` 309 | - `@enrico.piccinin/gitlab-tools` 310 | - `@eventcatalog/plugin-doc-generator-asyncapi` 311 | - `@eventcatalog/utils` 312 | - `@eventcatalogtest/plugin-doc-generator-asyncapi` 313 | - `@feizheng/react-markdown-props` 314 | - `@flive/react-kit` 315 | - `@gigsboat/cli` 316 | - `@gracexwho/model-card-generator` 317 | - `@haimmag/schema-tools` 318 | - `@hankanman/postgres-to-docs` 319 | - `@hitorisensei/markdown-readme-generator` 320 | - `@hitorisensei/monorepo-readme-generator` 321 | - `@jswork/react-markdown-props` 322 | - `@klarna/postgres-to-docs` 323 | - `@lm_fe/scripts` 324 | - `@medyll/css-fabric-helper` 325 | - `@mfbtech/changelog-generator` 326 | - `@microfleet/schema2md` 327 | - `@mjefi/instags` 328 | - `@oasis-engine/oasis-run` 329 | - `@opas/plugin-doc` 330 | - `@r6d9/kol-tracker` 331 | - `@r6d9/kol_tracker` 332 | - `@s-ui/changelog` 333 | - `@servable/manifest` 334 | - `@servable/tools` 335 | - `@shelex/schema-tools` 336 | - `@sidneys/releasenotes` 337 | - `@solarity/hardhat-markup` 338 | - `@solarity/hardhat-migrate` 339 | - `@truto/truto-jsonata` 340 | - `@wii/swagger-plugin-transform-doc` 341 | - `@xygengcn/koa-api-docs` 342 | - `@yesand/asterism` 343 | - `bookmark2md` 344 | - `ccdown` 345 | - `chdown` 346 | - `chdown-workers` 347 | - `cli-demo3` 348 | - `cloudcastsdown` 349 | - `codexer` 350 | - `collman` 351 | - `component-docs-2md` 352 | - `cwq` 353 | - `dargstack_rgen` 354 | - `describe-dependencies` 355 | - `doc-cli` 356 | - `doc-vue` 357 | - `doc-vue3` 358 | - `dokuinjs` 359 | - `eddown` 360 | - `flawed-code-scanner` 361 | - `galaxy-vuepress-docs` 362 | - `gatsby-source-gdocs2md` 363 | - `gatsby-source-google-docs` 364 | - `gatsby-source-google-docs-sheets` 365 | - `gatsby-source-google-docs-team` 366 | - `git-diff-llm` 367 | - `github-repo-tools` 368 | - `gridsome-source-google-docs` 369 | - `heat-sfdx-tooling` 370 | - `joi-md-doc` 371 | - `jumia-travel-changelog` 372 | - `kbase-components` 373 | - `kindle-highlights` 374 | - `kol-tracker` 375 | - `kol_analyzer` 376 | - `lab-changelog` 377 | - `lambda-docs-2md` 378 | - `laradown` 379 | - `lbdoc-p` 380 | - `lggn` 381 | - `machine-ip` 382 | - `make-postgres-markdown` 383 | - `mcp-douban-server` 384 | - `merak-compile` 385 | - `mokker` 386 | - `msdown` 387 | - `my_ccdown` 388 | - `mysql-ksdb` 389 | - `node-red-contrib-json2md` 390 | - `notion2mdblog` 391 | - `npm-ex-xpi` 392 | - `p2doc` 393 | - `pantheon_site_management` 394 | - `parse-google-docs-json` 395 | - `platzi-virtual-machine` 396 | - `postgres-markdown` 397 | - `rap2doc` 398 | - `react-docgen-markdown` 399 | - `reposier` 400 | - `rober19-config` 401 | - `rush-archive-project-plugin` 402 | - `sfhdown` 403 | - `solidity-benchmark` 404 | - `tcdown` 405 | - `terraform2md` 406 | - `type-graphql-to-md` 407 | - `utterance-to-markdown` 408 | - `uxcore-tools` 409 | - `vue-md-gen` 410 | - `vue2markdown` 411 | - `work-webpack`I am using this library to generate documentation for my projects, being integrated with [blah](https://github.com/IonicaBizau/node-blah). 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | ## :scroll: License 424 | 425 | [MIT][license] © [Ionică Bizău][website] 426 | 427 | 428 | 429 | 430 | 431 | 432 | [license]: /LICENSE 433 | [website]: https://ionicabizau.net 434 | [contributing]: /CONTRIBUTING.md 435 | [docs]: /DOCUMENTATION.md 436 | [badge_patreon]: https://ionicabizau.github.io/badges/patreon.svg 437 | [badge_amazon]: https://ionicabizau.github.io/badges/amazon.svg 438 | [badge_paypal]: https://ionicabizau.github.io/badges/paypal.svg 439 | [badge_paypal_donate]: https://ionicabizau.github.io/badges/paypal_donate.svg 440 | [patreon]: https://www.patreon.com/ionicabizau 441 | [amazon]: http://amzn.eu/hRo9sIZ 442 | [paypal-donations]: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=RVXDDLKKLQRJW 443 | -------------------------------------------------------------------------------- /dist/json2md.min.js: -------------------------------------------------------------------------------- 1 | "use strict";var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(n){return typeof n}:function(n){return n&&"function"==typeof Symbol&&n.constructor===Symbol?"symbol":typeof n};!function(n){if("object"===("undefined"==typeof exports?"undefined":_typeof(exports))&&"undefined"!=typeof module)module.exports=n();else if("function"==typeof define&&define.amd)define([],n);else{var e;e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,e.json2md=n()}}(function(){return function n(e,r,t){function o(u,f){if(!r[u]){if(!e[u]){var c="function"==typeof require&&require;if(!f&&c)return c(u,!0);if(i)return i(u,!0);var a=new Error("Cannot find module '"+u+"'");throw a.code="MODULE_NOT_FOUND",a}var s=r[u]={exports:{}};e[u][0].call(s.exports,function(n){var r=e[u][1][n];return o(r?r:n)},s,s.exports,n,e,r,t)}return r[u].exports}for(var i="function"==typeof require&&require,u=0;u/gi,e.strong).replace(/\<\/?bold\>/gi,e.strong).replace(/\<\/?em\>/gi,e.italic).replace(/\<\/?italic\>/gi,e.italic)};r.h1=t(1),r.h2=t(2),r.h3=t(3),r.h4=t(4),r.h5=t(5),r.h6=t(6),r.blockquote=function(n,e){return e(n,"> ")+"\n"},r.img=function(n,e){return Array.isArray(n)?e(n,"","img"):"string"==typeof n?r.img({source:n,title:""}):(n.title=n.title||"","!["+n.title+"]("+n.source+")\n")},r.ul=function(n,e){for(var r="",t=0;t 40 | // # JSON To Markdown 41 | // > A JSON to Markdown converter. 42 | // 43 | // ![Some image](https://example.com/some-image.png) 44 | // 45 | // ![Another image](https://example.com/some-image1.png) 46 | // 47 | // ![Yet another image](https://example.com/some-image2.png) 48 | // 49 | // ## Features 50 | // 51 | // - Easy to use 52 | // - You can programmatically generate Markdown content 53 | // - ... 54 | // 55 | // ## How to contribute 56 | // 57 | // 1. Fork the project 58 | // 2. Create your branch 59 | // 3. Raise a pull request 60 | // 61 | // ## Code blocks 62 | // 63 | // Below you can see a code block example. 64 | // 65 | // ```js 66 | // function sum (a, b) { 67 | // return a + b 68 | // } 69 | // sum(1, 2) 70 | // ``` 71 | -------------------------------------------------------------------------------- /lib/converters.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | const converters = module.exports = {} 4 | 5 | let generateHeader = repeat => { 6 | return (input, json2md) => { 7 | return "#".repeat(repeat) + " " + json2md(input) 8 | } 9 | } 10 | 11 | let indent = (content, spaces, ignoreFirst) => { 12 | let lines = content 13 | 14 | if (typeof content === "string") { 15 | lines = content.split("\n") 16 | } 17 | 18 | if (ignoreFirst) { 19 | if (lines.length <= 1) { 20 | return lines.join("\n") 21 | } 22 | return lines[0] + "\n" + indent(lines.slice(1), spaces, false) 23 | } 24 | 25 | return lines.map(c => " ".repeat(spaces) + c).join("\n") 26 | } 27 | 28 | let parseTextFormat = text => { 29 | 30 | let formats = { 31 | strong: "**" 32 | , italic: "*" 33 | , underline: "_" 34 | , strikethrough: "~~" 35 | } 36 | 37 | return text 38 | .replace(/<\/?strong\>/gi, formats.strong) 39 | .replace(/<\/?bold\>/gi, formats.strong) 40 | .replace(/<\/?em\>/gi, formats.italic) 41 | .replace(/<\/?italic\>/gi, formats.italic) 42 | .replace(/<\/?u\>/gi, formats.underline) 43 | .replace(/<\/?strike\>/gi, formats.strikethrough) 44 | 45 | } 46 | 47 | // Headings 48 | converters.h1 = generateHeader(1) 49 | converters.h2 = generateHeader(2) 50 | converters.h3 = generateHeader(3) 51 | converters.h4 = generateHeader(4) 52 | converters.h5 = generateHeader(5) 53 | converters.h6 = generateHeader(6) 54 | 55 | converters.blockquote = (input, json2md) => { 56 | return json2md(input, "> ") 57 | } 58 | 59 | converters.img = (input, json2md) => { 60 | 61 | debugger 62 | if (Array.isArray(input)) { 63 | return json2md(input, "", "img") 64 | } 65 | if (typeof input === "string") { 66 | return converters.img({ source: input, title: "", alt: ""}) 67 | } 68 | input.title = input.title || "" 69 | input.alt = input.alt || "" 70 | return "![" + input.alt + "](" + input.source + " \"" + input.title + "\")" 71 | } 72 | 73 | converters.ul = (input, json2md) => { 74 | let c = "" 75 | for (let i = 0; i < input.length; ++i) { 76 | let marker = "" 77 | 78 | let type = Object.keys(input[i])[0] 79 | if(type !== "ul" && type !== "ol" && type !== 'taskLists'){ 80 | marker += "\n - " 81 | } 82 | 83 | c += marker + parseTextFormat(indent(json2md(input[i]), 4, true)) 84 | } 85 | return c 86 | } 87 | 88 | converters.ol = (input, json2md) => { 89 | let c = "" 90 | let jumpCount = 0 91 | for (let i = 0; i < input.length; ++i) { 92 | let marker = "" 93 | let type = Object.keys(input[i])[0] 94 | if(type !== "ul" && type !== "ol" && type !== 'taskLists'){ 95 | marker = "\n " + (i + 1 - jumpCount) + ". " 96 | } else { 97 | jumpCount++ 98 | } 99 | 100 | c += marker + parseTextFormat(indent(json2md(input[i]), 4, true)) 101 | } 102 | return c 103 | } 104 | 105 | converters.taskLists = (input, json2md) => { 106 | let c = "" 107 | for (let i = 0; i < input.length; ++i) { 108 | let marker = "" 109 | 110 | let type = Object.keys(input[i])[0] 111 | if(type !== "ul" && type !== "ol" && type !== 'taskLists'){ 112 | marker += input[i].isDone ? "\n - [x] " : "\n - [ ] " 113 | } 114 | 115 | c += marker + parseTextFormat(indent(json2md(input[i].title || input[i]), 4, true)) 116 | } 117 | return c 118 | } 119 | 120 | converters.code = (input, json2md) => { 121 | let c = "```" + (input.language || "") + "\n" 122 | if (Array.isArray(input.content)) { 123 | c += input.content.join("\n") 124 | } else { 125 | c += input.content 126 | } 127 | c += "\n```" 128 | return c 129 | } 130 | 131 | converters.p = (input, json2md) => { 132 | return parseTextFormat(json2md(input, "\n")) 133 | } 134 | 135 | converters.table = (input, json2md) => { 136 | 137 | const ALIGNMENT = { 138 | CENTER: 'center' 139 | , RIGHT: 'right' 140 | , LEFT: 'left' 141 | , NONE: 'none' 142 | } 143 | 144 | const PREFERRED_LENGTH_PER_ALIGNMENT = { 145 | [ALIGNMENT.CENTER]: 3 146 | , [ALIGNMENT.RIGHT]: 2 147 | , [ALIGNMENT.LEFT]: 2 148 | , [ALIGNMENT.NONE]: 1 149 | } 150 | 151 | if (typeof input !== "object" 152 | || !input.hasOwnProperty("headers") 153 | || !input.hasOwnProperty("rows")) { 154 | return "" 155 | } 156 | 157 | const alignment = input.headers.map((_, index) => input.aligns && input.aligns[index] 158 | ? input.aligns[index] 159 | : ALIGNMENT.NONE 160 | ) 161 | 162 | // try to match the space the column name and the dashes (and colons) take up. Minimum depends on alignment 163 | const preferred_lengths = input.headers.map((header, index) => Math.max( 164 | PREFERRED_LENGTH_PER_ALIGNMENT[alignment[index]], 165 | header.length - 2 166 | )) 167 | 168 | if (input.pretty === true) { 169 | // update preferred_lengths considering rows' cells length 170 | input.rows.forEach(row => { 171 | (Array.isArray(row) ? row : input.headers.map(col_id => row[col_id])) 172 | .forEach((cell, index) => { 173 | preferred_lengths[index] = Math.max(preferred_lengths[index], String(cell).length-2) 174 | }) 175 | }) 176 | } 177 | 178 | const fill_right = function(diff, header) { 179 | return " ".repeat(diff) + header; 180 | } 181 | const fill_left = function(diff, header) { 182 | return header + " ".repeat(diff); 183 | } 184 | const fill_center = function(diff, header) { 185 | return " ".repeat(Math.floor(diff/2)) + header + " ".repeat(Math.ceil(diff/2)); 186 | } 187 | 188 | const fill_th = (header, index) => { 189 | const diff = preferred_lengths[index]+2 - header.length; 190 | switch (alignment[index]) { 191 | case ALIGNMENT.RIGHT: return fill_right(diff, header); 192 | case ALIGNMENT.LEFT: return fill_left(diff, header); 193 | case ALIGNMENT.CENTER: 194 | case ALIGNMENT.NONE: 195 | default: return fill_center(diff, header); 196 | } 197 | }; 198 | 199 | const fill_td = (header, index) => { 200 | const diff = preferred_lengths[index]+2 - header.length; 201 | switch (alignment[index]) { 202 | case ALIGNMENT.RIGHT: return fill_right(diff, header); 203 | case ALIGNMENT.NONE: 204 | case ALIGNMENT.LEFT: return fill_left(diff, header); 205 | case ALIGNMENT.CENTER: 206 | default: return fill_center(diff, header); 207 | } 208 | }; 209 | 210 | // add spaces around column name if necessary (side(s) depends on alignment) 211 | const column_names = input.headers.map(fill_th) 212 | 213 | const header = "| " + column_names.join(" | ") + " |"; 214 | 215 | const spaces = "| " + input.headers.map((_, index) => { 216 | const inner = "-".repeat(preferred_lengths[index]) 217 | switch (alignment[index]) { 218 | case ALIGNMENT.CENTER: return ":" + inner + ":"; 219 | case ALIGNMENT.RIGHT: return "-" + inner + ":"; 220 | case ALIGNMENT.LEFT: return ":" + inner + "-"; 221 | case ALIGNMENT.NONE: 222 | default: return "-" + inner + "-"; 223 | } 224 | }).join(" | ") + " |"; 225 | 226 | const fill_tbody_cell = (cell, index) => { 227 | if(input.pretty !== true) return cell; 228 | return fill_td(cell, index); 229 | } 230 | 231 | const data = input.rows.map(row => 232 | "| " + (Array.isArray(row) ? row : input.headers.map(col_id => row[col_id])) 233 | .map(cell => json2md(cell)) 234 | .map(cell => parseTextFormat(cell)) 235 | .map(cell => cell.replace(/([^\\])\|/g, "$1\\|")) 236 | .map(cell => cell.trim()) 237 | .map(fill_tbody_cell) 238 | .join(" | ") + " |" 239 | ).join("\n"); 240 | 241 | return [header, spaces, data].join("\n") 242 | } 243 | 244 | converters.link = (input, json2md) => { 245 | if (Array.isArray(input)) { 246 | return json2md(input, "", "link") 247 | } 248 | if (typeof input === "string") { 249 | return converters.link({ source: input, title: "" }) 250 | } 251 | return "[" + input.title + "](" + input.source + ")" 252 | } 253 | 254 | converters.hr = (input, json2md) => { 255 | return '---' 256 | } 257 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | const converters = require("./converters") 4 | , indento = require("indento") 5 | 6 | /** 7 | * json2md 8 | * Converts a JSON input to markdown. 9 | * 10 | * **Supported elements** 11 | * 12 | * | Type | Element | Data | Example | 13 | * |--------------|--------------------|--------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------| 14 | * | `h1` | Heading 1 | The heading text as string. | `{ h1: "heading 1" }` | 15 | * | `h2` | Heading 2 | The heading text as string. | `{ h2: "heading 2" }` | 16 | * | `h3` | Heading 3 | The heading text as string. | `{ h3: "heading 3" }` | 17 | * | `h4` | Heading 4 | The heading text as string. | `{ h4: "heading 4" }` | 18 | * | `h5` | Heading 5 | The heading text as string. | `{ h5: "heading 5" }` | 19 | * | `h6` | Heading 6 | The heading text as string. | `{ h6: "heading 6" }` | 20 | * | `p` | Paragraphs | The paragraph text as string or array (multiple paragraphs). | `{ p: "Hello World"}` or multiple paragraphs: `{ p: ["Hello", "World"] }` | 21 | * | `blockquote` | Blockquote | The blockquote as string or array (multiple blockquotes) | `{ blockquote: "Hello World"}` or multiple blockquotes: `{ blockquote: ["Hello", "World"] }` | 22 | * | `img` | Image | An object or an array of objects containing the `title`, `source` and `alt` fields. | `{ img: { title: "My image title", source: "http://example.com/image.png", alt: "My image alt" } }` | 23 | * | `ul` | Unordered list | An array of strings or lists representing the items. | `{ ul: ["item 1", "item 2"] }` | 24 | * | `ol` | Ordered list | An array of strings or lists representing the items. | `{ ol: ["item 1", "item 2"] }` | 25 | * | `hr` | Separator | None | `{ hr: "" }` | 26 | * | `code` | Code block element | An object containing the `language` (`String`) and `content` (`Array` or `String`) fields. | `{ code: { "language": "html", "content": "" } }` | 27 | * | `table` | Table | An object containing the `headers` (`Array` of `String`s) and `rows` (`Array` of `Array`s or `Object`s). | `{ table: { headers: ["a", "b"], rows: [{ a: "col1", b: "col2" }] } }` or `{ table: { headers: ["a", "b"], rows: [["col1", "col2"]] } }` | 28 | * | `link` | Link | An object containing the `title` and the `source` fields. | `{ title: 'hello', source: 'https://ionicabizau.net' }` | 29 | * 30 | * 31 | * You can extend the `json2md.converters` object to support your custom types. 32 | * 33 | * ```js 34 | * json2md.converters.sayHello = function (input, json2md) { 35 | * return "Hello " + input + "!" 36 | * } 37 | * ``` 38 | * 39 | * Then you can use it: 40 | * 41 | * ```js 42 | * json2md({ sayHello: "World" }) 43 | * // => "Hello World!" 44 | * ``` 45 | * 46 | * @name json2md 47 | * @function 48 | * @param {Array|Object|String} data The input JSON data. 49 | * @param {String} prefix A snippet to add before each line. 50 | * @return {String} The generated markdown result. 51 | */ 52 | function json2md(data, prefix, _type) { 53 | prefix = prefix || "" 54 | if (typeof data === "string" || typeof data === "number") { 55 | return indento(data, 1, prefix) 56 | } 57 | 58 | let content = [] 59 | 60 | // Handle arrays 61 | if (Array.isArray(data)) { 62 | for (let i = 0; i < data.length; ++i) { 63 | content.push(indento(json2md(data[i], "", _type), 1, prefix)) 64 | } 65 | return content.join("\n") 66 | } else if (_type) { 67 | let mdText = ""; 68 | let func = converters[_type || type]; 69 | if (typeof func === "function") { 70 | mdText += indento(func(_type ? data : data[type], json2md), 1, prefix) + "\n"; 71 | } else { 72 | throw new Error("There is no such converter: " + type); 73 | } 74 | return mdText 75 | } else { 76 | let mdText = ""; 77 | Object.keys(data).forEach((type, index, array) => { 78 | let func = converters[_type || type]; 79 | 80 | if (typeof func === "function") { 81 | mdText += indento(func(_type ? data : data[type], json2md), 1, prefix) + "\n"; 82 | } else { 83 | throw new Error("There is no such converter: " + type); 84 | } 85 | }); 86 | return mdText; 87 | } 88 | } 89 | 90 | /** 91 | * @param {Array|Object|String} data The input JSON data. 92 | * @param {String} prefix A snippet to add before each line. 93 | * @return {Promise.} The generated markdown result. 94 | */ 95 | json2md.async = (data, prefix, _type) => Promise.resolve().then(() => { 96 | prefix = prefix || "" 97 | if (typeof data === "string" || typeof data === "number") { 98 | return indento(data, 1, prefix) 99 | } 100 | 101 | let content = [] 102 | 103 | // Handle arrays 104 | if (Array.isArray(data)) { 105 | const promises = data.map((d, index) => Promise.resolve() 106 | .then(() => json2md.async(d, "", _type)) 107 | .then((result) => indento(result, 1, prefix)) 108 | .then((result) => { 109 | content[index] = result; 110 | }) 111 | ) 112 | return Promise.all(promises).then(() => content.join("\n")) 113 | } else { 114 | let type = Object.keys(data)[0] 115 | , func = converters[_type || type] 116 | 117 | if (typeof func === "function") { 118 | return Promise.resolve() 119 | .then(() => func(_type ? data : data[type], json2md)) 120 | .then((result) => indento(result, 1, prefix) + "\n") 121 | } 122 | throw new Error("There is no such converter: " + type) 123 | } 124 | }) 125 | 126 | json2md.converters = converters 127 | 128 | module.exports = json2md 129 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json2md", 3 | "version": "2.0.3", 4 | "description": "A JSON to Markdown converter.", 5 | "main": "lib/index.js", 6 | "directories": { 7 | "example": "example", 8 | "test": "test" 9 | }, 10 | "scripts": { 11 | "test": "node test" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+ssh://git@github.com/IonicaBizau/json2md.git" 16 | }, 17 | "keywords": [ 18 | "json", 19 | "markdown", 20 | "converter", 21 | "generator" 22 | ], 23 | "author": "Ionică Bizău (https://ionicabizau.net)", 24 | "contributors": [ 25 | "Crisoforo Gaspar ", 26 | "Dmitry Tsvettsikh ", 27 | "Daniel Bastos ", 28 | "Keith Chen ", 29 | "Cédric Delpoux ", 30 | "Jan-Philipp Steghöfer ", 31 | "Khalil Yao <945947732@qq.com>" 32 | ], 33 | "license": "MIT", 34 | "bugs": { 35 | "url": "https://github.com/IonicaBizau/json2md/issues" 36 | }, 37 | "homepage": "https://github.com/IonicaBizau/json2md#readme", 38 | "blah": { 39 | "h_img": "http://i.imgur.com/uj64JFw.png", 40 | "description": "If you're looking to use this on the client side, that's also possible. Check out the [`dist`](/dist) directory.", 41 | "usages": [ 42 | "I am using this library to generate documentation for my projects, being integrated with [blah](https://github.com/IonicaBizau/node-blah)." 43 | ] 44 | }, 45 | "dependencies": { 46 | "indento": "^1.1.13" 47 | }, 48 | "devDependencies": { 49 | "tester": "^1.4.5" 50 | }, 51 | "files": [ 52 | "bin/", 53 | "app/", 54 | "lib/", 55 | "dist/", 56 | "src/", 57 | "scripts/", 58 | "resources/", 59 | "menu/", 60 | "cli.js", 61 | "index.js", 62 | "index.d.ts", 63 | "package-lock.json", 64 | "bloggify.js", 65 | "bloggify.json", 66 | "bloggify/" 67 | ] 68 | } -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | var json2md = require("../lib"), 3 | tester = require("tester"); 4 | 5 | tester.describe("json2md", test => { 6 | // Headings 7 | test.it("should support headings", function(cb) { 8 | test.expect(json2md({ 9 | h1: "Heading 1" 10 | })).toBe("# Heading 1\n"); 11 | test.expect(json2md({ 12 | h2: "Heading 2" 13 | })).toBe("## Heading 2\n"); 14 | test.expect(json2md({ 15 | h3: "Heading 3" 16 | })).toBe("### Heading 3\n"); 17 | test.expect(json2md({ 18 | h4: "Heading 4" 19 | })).toBe("#### Heading 4\n"); 20 | test.expect(json2md({ 21 | h5: "Heading 5" 22 | })).toBe("##### Heading 5\n"); 23 | test.expect(json2md({ 24 | h6: "Heading 6" 25 | })).toBe("###### Heading 6\n"); 26 | cb(); 27 | }); 28 | 29 | // Blockquote 30 | test.it("should support blockquotes", function(cb) { 31 | test.expect(json2md({ 32 | blockquote: "Some content" 33 | })).toBe("> Some content\n"); 34 | cb(); 35 | }); 36 | 37 | // Images 38 | test.it("should support images", function(cb) { 39 | test.expect(json2md({ 40 | img: { 41 | source: "source", 42 | title: "title", 43 | alt: 'alt', 44 | } 45 | })).toBe('![alt](source "title")\n'); 46 | cb(); 47 | }); 48 | 49 | // Image array 50 | test.it("should support an array of images", function(cb) { 51 | test.expect(json2md({ 52 | img:[{ 53 | source: "source", 54 | title: "title", 55 | alt: 'alt', 56 | }, { 57 | source: "sauce", 58 | title: "heading", 59 | alt: 'salt', 60 | }] 61 | })).toBe([ 62 | '![alt](source "title")', 63 | '', 64 | '![salt](sauce "heading")', 65 | '', 66 | '', 67 | ].join('\n')); 68 | cb(); 69 | }); 70 | 71 | // Links 72 | test.it("should support links", function(cb) { 73 | test.expect(json2md({ 74 | link: { 75 | source: "source", 76 | title: "title" 77 | } 78 | })).toBe("[title](source)\n"); 79 | cb(); 80 | }); 81 | 82 | // Horizontal rule 83 | test.it("should support horizontal rule", function(cb) { 84 | test.expect(json2md({ 85 | hr: '' 86 | })).toBe("---\n"); 87 | cb(); 88 | }); 89 | 90 | // Unordered lists 91 | test.it("should support unordered lists", function(cb) { 92 | test.expect(json2md({ 93 | ul: [ 94 | "item 1", "item 2" 95 | ] 96 | })).toBe("\n - item 1\n - item 2\n"); 97 | cb(); 98 | }); 99 | 100 | test.it("should support unordered lists with emphasis format", function(cb) { 101 | test.expect(json2md({ 102 | ul: [ 103 | "item 1", "item 2" 104 | ] 105 | })).toBe("\n - *item 1*\n - **item 2**\n"); 106 | cb(); 107 | }); 108 | 109 | // Ordered lists 110 | test.it("should support ordered lists", function(cb) { 111 | test.expect(json2md({ 112 | ol: [ 113 | "item 1", "item 2" 114 | ] 115 | })).toBe("\n 1. item 1\n 2. item 2\n"); 116 | cb(); 117 | }); 118 | 119 | // task lists 120 | test.it("should support task lists", function(cb) { 121 | test.expect(json2md({ 122 | taskLists: [ 123 | { title: "item 1" }, { title: "item 2", isDone: true }, "item 3" 124 | ] 125 | })).toBe("\n - [ ] item 1\n - [x] item 2\n - [ ] item 3\n"); 126 | cb(); 127 | }); 128 | 129 | // Code blocks 130 | test.it("should support code blocks", function(cb) { 131 | test.expect(json2md({ 132 | code: { 133 | language: "js", 134 | content: [ 135 | "function sum (a, b) {", 136 | " return a + b;", 137 | "}", 138 | "sum(1, 2);" 139 | ] 140 | } 141 | })).toBe("```js\nfunction sum (a, b) {\n return a + b;\n}\nsum(1, 2);\n```\n"); 142 | cb(); 143 | }); 144 | 145 | // Paragraphs 146 | test.it("should support paragraphs", function(cb) { 147 | test.expect(json2md({ 148 | p: [ 149 | "Two", "Paragraphs" 150 | ] 151 | })).toBe("\nTwo\n\nParagraphs\n"); 152 | cb(); 153 | }); 154 | 155 | test.it("should support paragraphs with bold text", function(cb) { 156 | test.expect(json2md({ 157 | p: [ 158 | "Two more words", "in this paragraph, right?" 159 | ] 160 | })).toBe("\nTwo **more words**\n\nin this paragraph, **right?**\n"); 161 | cb(); 162 | }); 163 | 164 | test.it("should support paragraphs with underline", function(cb) { 165 | test.expect(json2md({ 166 | p: [ 167 | "Two more words", "in this paragraph, right?" 168 | ] 169 | })).toBe("\nTwo _more words_\n\nin this paragraph, _right?_\n"); 170 | cb(); 171 | }); 172 | 173 | test.it("should support paragraphs with strikethrough", function(cb) { 174 | test.expect(json2md({ 175 | p: [ 176 | "Two more words", "in this paragraph, right?" 177 | ] 178 | })).toBe("\nTwo ~~more words~~\n\nin this paragraph, ~~right?~~\n"); 179 | cb(); 180 | }); 181 | 182 | // Custom converters 183 | test.it("should support custom types", function(cb) { 184 | json2md.converters.sayHello = function(input, json2md) { 185 | return "Hello " + input + "!"; 186 | }; 187 | test.expect(json2md({ 188 | sayHello: "World" 189 | })).toBe("Hello World!\n") 190 | cb(); 191 | }); 192 | 193 | // Code blocks in lists 194 | test.it("should correctly indent code blocks in lists", function(cb) { 195 | test.expect(json2md({ 196 | ol: [ 197 | [ 198 | "Copy the code below:", { 199 | code: { 200 | language: "js", 201 | content: [ 202 | "function sum (a, b) {", 203 | " return a + b;", 204 | "}", 205 | "sum(1, 2);" 206 | ] 207 | } 208 | } 209 | ] 210 | ] 211 | })).toBe(` 212 | 1. Copy the code below: 213 | \`\`\`js 214 | function sum (a, b) { 215 | return a + b; 216 | } 217 | sum(1, 2); 218 | \`\`\` 219 | \n`); 220 | cb(); 221 | }); 222 | 223 | test.it("should correctly indent code blocks in unordered lists", function(cb) { 224 | test.expect(json2md({ 225 | ul: [ 226 | [ 227 | "Copy the code below:", { 228 | code: { 229 | language: "js", 230 | content: [ 231 | "function sum (a, b) {", " return a + b;", "}", "sum(1, 2);" 232 | ] 233 | } 234 | } 235 | ] 236 | ] 237 | })).toBe(` 238 | - Copy the code below: 239 | \`\`\`js 240 | function sum (a, b) { 241 | return a + b; 242 | } 243 | sum(1, 2); 244 | \`\`\` 245 | \n`); 246 | cb(); 247 | }); 248 | 249 | test.it("should work when input is number", function(cb) { 250 | test.expect(json2md({ 251 | blockquote: 123 252 | })).toBe("> 123\n"); 253 | cb(); 254 | }); 255 | 256 | test.it("should support tables, rows is objects", function(cb) { 257 | test.expect(json2md({ 258 | table: { 259 | headers: ["a", "b"], 260 | rows: [{ 261 | a: "col1", 262 | b: "col2" 263 | }, { 264 | a: "col1", 265 | b: "col2 very long" 266 | }] 267 | } 268 | })).toBe("| a | b |\n| --- | --- |\n| col1 | col2 |\n| col1 | col2 very long |\n"); 269 | cb(); 270 | }) 271 | 272 | test.it("should support tables, rows is arrays", function(cb) { 273 | test.expect(json2md({ 274 | table: { 275 | headers: ["a", "b"], 276 | rows: [ 277 | ["col1", "col2"], 278 | ["col1", "col2"] 279 | ] 280 | } 281 | })).toBe("| a | b |\n| --- | --- |\n| col1 | col2 |\n| col1 | col2 |\n"); 282 | cb(); 283 | }) 284 | 285 | test.it("should support tables aligns", function(cb) { 286 | test.expect(json2md({ 287 | table: { 288 | headers: ["a", "b", "c", "d"], 289 | rows: [ 290 | ["col1", "col2", "col3", "col4"] 291 | ], 292 | aligns: ["", "center", "left", "right"], 293 | } 294 | })).toBe("| a | b | c | d |\n| --- | :---: | :--- | ---: |\n| col1 | col2 | col3 | col4 |\n"); 295 | cb(); 296 | }) 297 | 298 | test.it("should support tables and match column name length with dashes", function(cb) { 299 | test.expect(json2md({ 300 | table: { 301 | headers: ["name", "amount", "somesuperlongword", "a"], 302 | rows: [ 303 | ["col1", "col2", "col3", "col4"] 304 | ], 305 | aligns: ["", "center", "left", "right"], 306 | } 307 | })).toBe("| name | amount | somesuperlongword | a |\n| ---- | :----: | :---------------- | ---: |\n| col1 | col2 | col3 | col4 |\n"); 308 | cb(); 309 | }) 310 | 311 | test.it("should support tables and escape any \"|\"'s", function(cb) { 312 | test.expect(json2md({ 313 | table: { 314 | headers: ["a", "b"], 315 | rows: [ 316 | ["col|1|2", "col\\|3\\|4"] 317 | ] 318 | } 319 | })).toBe("| a | b |\n| --- | --- |\n| col\\|1\\|2 | col\\|3\\|4 |\n"); 320 | cb(); 321 | }) 322 | 323 | test.it("should support pretty tables, rows is objects", function(cb) { 324 | test.expect(json2md({ 325 | table: { 326 | pretty: true, 327 | headers: ["a", "b"], 328 | rows: [{ 329 | a: 1000, 330 | b: "col2" 331 | }, { 332 | a: 1000, 333 | b: "col2 very long" 334 | }] 335 | } 336 | })).toBe(`| a | b | 337 | | ---- | -------------- | 338 | | 1000 | col2 | 339 | | 1000 | col2 very long | 340 | `); 341 | cb(); 342 | }) 343 | 344 | test.it("should support pretty tables, rows is array", function(cb) { 345 | test.expect(json2md({ 346 | table: { 347 | pretty: true, 348 | headers: ["a", "b"], 349 | rows: [ 350 | ["col1", "col2"], 351 | ["col1", "col2 very long"] 352 | ] 353 | } 354 | })).toBe(`| a | b | 355 | | ---- | -------------- | 356 | | col1 | col2 | 357 | | col1 | col2 very long | 358 | `); 359 | cb(); 360 | }) 361 | 362 | test.it("should support pretty tables aligns", function(cb) { 363 | test.expect(json2md({ 364 | table: { 365 | pretty: true, 366 | aligns: ['left', 'right'], 367 | headers: ["a", "b"], 368 | rows: [ 369 | ["col1", "col2"], 370 | ["col1", "col2 very long"] 371 | ] 372 | } 373 | })).toBe(`| a | b | 374 | | :--- | -------------: | 375 | | col1 | col2 | 376 | | col1 | col2 very long | 377 | `); 378 | cb(); 379 | }) 380 | 381 | test.it("should support several top-level object keys", 382 | function(cb) { 383 | json2md.converters.sayHello = function(input, json2md) { 384 | return "Hello " + input + "!"; 385 | }; 386 | test.expect(json2md({ 387 | sayHello: "World", 388 | h1: "Hello Friends!" 389 | })).toBe("Hello World!\n# Hello Friends!\n") 390 | cb(); 391 | } 392 | ); 393 | 394 | test.it("should support tables with links", function(cb) { 395 | test.expect(json2md({ 396 | table: { 397 | headers: ["a", "b"], 398 | rows: [ 399 | { 400 | a: { 401 | link: { 402 | title: "aTitle", 403 | source: "http://www.example.com" 404 | } 405 | }, 406 | b: "col2" 407 | } 408 | ], 409 | } 410 | })).toBe('| a | b |\n| --- | --- |\n| [aTitle](http://www.example.com) | col2 |\n'); 411 | cb(); 412 | }) 413 | }); 414 | 415 | tester.describe("json2md.async", test => { 416 | test.it("should return a Promise instance", function(cb) { 417 | const p = json2md.async({ 418 | h1: "Heading 1" 419 | }).then(function (result) { 420 | test.expect(result).toBe("# Heading 1\n"); 421 | cb(); 422 | }).catch(function (err) { 423 | cb(err) 424 | }); 425 | test.expect(p instanceof Promise).toBe(true); 426 | }); 427 | 428 | test.it("should accept an array", function(cb) { 429 | const p = json2md.async([ 430 | { h1: "Heading 1" }, 431 | { h2: "Heading 2" }, 432 | ]).then(function (result) { 433 | test.expect(result).toBe("# Heading 1\n\n## Heading 2\n"); 434 | cb(); 435 | }).catch(function (err) { 436 | cb(err) 437 | }); 438 | test.expect(p instanceof Promise).toBe(true); 439 | }); 440 | 441 | test.it("should have same behaviors to original json2md", function(cb) { 442 | Promise.all([ 443 | json2md.async({ 444 | h1: "Heading 1" 445 | }).then((result) => { 446 | test.expect(result).toBe("# Heading 1\n"); 447 | }), 448 | 449 | json2md.async({ 450 | h2: "Heading 2" 451 | }).then((result) => { 452 | test.expect(result).toBe("## Heading 2\n"); 453 | }), 454 | 455 | json2md.async({ 456 | h3: "Heading 3" 457 | }).then((result) => { 458 | test.expect(result).toBe("### Heading 3\n"); 459 | }), 460 | 461 | json2md.async({ 462 | blockquote: "Some content" 463 | }).then((result) => { 464 | test.expect(result).toBe("> Some content\n"); 465 | }), 466 | ]).then(function () { 467 | cb(); 468 | }).catch(function (err) { 469 | cb(err) 470 | }); 471 | }); 472 | 473 | test.it("should support custom types which have same behaviors to original json2md", function(cb) { 474 | json2md.converters.sayHello = function(input, json2md) { 475 | return "Hello " + input + "!"; 476 | }; 477 | 478 | json2md.async({ 479 | sayHello: "World" 480 | }).then((result) => { 481 | test.expect(result).toBe("Hello World!\n"); 482 | cb(); 483 | }).catch(function (err) { 484 | cb(err) 485 | }); 486 | }); 487 | 488 | test.it("should support async converter", function(cb) { 489 | json2md.converters.asyncConvert = function(input, json2md) { 490 | return new Promise(function(resolve) { 491 | setTimeout(function() { 492 | resolve("Hello " + input + "!"); 493 | }, 100); 494 | }); 495 | }; 496 | 497 | json2md.async({ 498 | asyncConvert: "World" 499 | }).then((result) => { 500 | test.expect(result).toBe("Hello World!\n"); 501 | cb(); 502 | }).catch(function (err) { 503 | cb(err) 504 | }); 505 | }); 506 | 507 | test.it("should keep order when async converters finished at different times", function(cb) { 508 | json2md.converters.asyncConvert2 = function(input, json2md) { 509 | return new Promise(function(resolve) { 510 | setTimeout(function() { 511 | resolve("Hello " + input.text + "!"); 512 | }, input.timeout); 513 | }); 514 | }; 515 | 516 | Promise.all([ 517 | json2md.async([ 518 | { h1: "Heading 1" }, 519 | { asyncConvert2: {text: "World", timeout: 200} }, 520 | { h2: "Heading 2" }, 521 | { asyncConvert2: {text: "hello", timeout: 100} }, 522 | ]).then((result) => { 523 | test.expect(result).toBe("# Heading 1\n\nHello World!\n\n## Heading 2\n\nHello hello!\n"); 524 | }), 525 | ]).then(function () { 526 | cb(); 527 | }).catch(function (err) { 528 | cb(err) 529 | }); 530 | }); 531 | }); 532 | --------------------------------------------------------------------------------