Skip to content

List Converter — JavaScript source

Transform a list between separators (newline, comma, space, pipe, semicolon, tab) with trim, dedupe, sort, and empty-removal options. Runs entirely in your browser, with a shareable link.

This is the JavaScript implementation — the same logic the interactive tool runs, in a shareable, citable form.

/**
 * list-converter - convert lists between separators (newline/comma/space/pipe/...).
 *
 * Language:   JavaScript (ES2020+, runs unmodified in Node 16+ and modern browsers)
 * Source:     CosmoDev polyglot showcase port of the List Converter tool, ported
 *             from cli/list-converter/list-converter.go (the Go CLI twin),
 *             which is itself the lock-step mirror of src/lib/list-converter.ts
 *             (the canonical TypeScript implementation).
 * License:    display source - part of CosmoDev's polyglot tool pages.
 *
 * Design goals:
 *   - Pure + deterministic; never throws.
 *   - Functionally equivalent to the Go twin: same inputs -> same outputs.
 *   - Self-contained: stdlib only (no npm dependencies).
 *
 * Pipeline: split on the "from" separator -> (trim each item) -> (drop empties)
 * -> (dedup, optionally case-insensitive, first occurrence wins) -> (stable
 * sort, optionally case-insensitive) -> join with the "to" separator. The
 * Separator set and options mirror the Go types exactly; defaults are
 * from=newline, to=comma (the TS resolveSep fallbacks).
 */

'use strict';

/**
 * The built-in separators, keyed by the TS `Separator` name. Mirrors Go's
 * `sepString` switch - the value is the literal split/join glue.
 */
const SEP_CHAR = Object.freeze({
  newline: '\n',
  comma: ',',
  space: ' ',
  pipe: '|',
  semicolon: ';',
  tab: '\t',
});

/**
 * Resolve a separator name (or a literal custom string) to its glue. Unknown
 * keys fall through as a literal separator, matching the TS `resolveSep`
 * behavior; the enum-only Go twin simply never exercises that path.
 * @param {string} [name]
 * @param {string} fallback
 * @returns {string}
 */
function resolveSep(name, fallback) {
  if (name === undefined) return SEP_CHAR[fallback];
  if (Object.prototype.hasOwnProperty.call(SEP_CHAR, name)) return SEP_CHAR[name];
  return name;
}

/**
 * Options shape. Keys are all optional.
 * @typedef {Object} ListOptions
 * @property {('newline'|'comma'|'space'|'pipe'|'semicolon'|'tab')|string} [from]   Source separator. Defaults to 'newline'.
 * @property {('newline'|'comma'|'space'|'pipe'|'semicolon'|'tab')|string} [to]     Target separator. Defaults to 'comma'.
 * @property {boolean} [trim]            Trim each split item (before removeEmpty). Defaults to false.
 * @property {boolean} [removeEmpty]     Drop items equal to ''. Defaults to false.
 * @property {boolean} [unique]          Keep the first occurrence of each item. Defaults to false.
 * @property {boolean} [sort]            Stable-sort ascending. Defaults to false.
 * @property {boolean} [caseInsensitive] Lowercase keys for unique/sort; keep original casing in output. Defaults to false.
 */

/**
 * Convert a list between separators. Never throws; an empty `input` simply
 * yields a one-item list joined back together (mirroring Go's
 * `strings.Split("", sep) == [""]`).
 *
 * @param {string} input
 * @param {ListOptions} [options={}]
 * @returns {string}
 */
function convertList(input, options = {}) {
  const fromCh = resolveSep(options.from, 'newline');
  const toCh = resolveSep(options.to, 'comma');

  let items = input.split(fromCh);
  if (options.trim) items = items.map((s) => s.trim());
  if (options.removeEmpty) items = items.filter((s) => s !== '');
  if (options.unique) {
    // Keyed by the (optionally lowercased) value; the first-seen item
    // survives with its original casing. Set gives O(1) membership.
    const seen = new Set();
    items = items.filter((s) => {
      const key = options.caseInsensitive ? s.toLowerCase() : s;
      if (seen.has(key)) return false;
      seen.add(key);
      return true;
    });
  }
  if (options.sort) {
    // Array.prototype.sort is stable in ES2019+. We lowercase only for the
    // comparison when caseInsensitive is set, leaving the stored values as-is.
    items = [...items].sort((a, b) => {
      const ka = options.caseInsensitive ? a.toLowerCase() : a;
      const kb = options.caseInsensitive ? b.toLowerCase() : b;
      return ka < kb ? -1 : ka > kb ? 1 : 0;
    });
  }
  return items.join(toCh);
}

/**
 * Convenience wrapper using default options - the common "newline list to a
 * comma list" case (matches the tool's default From/To).
 *
 * @param {string} input
 * @returns {string}
 */
function convertListDefault(input) {
  return convertList(input, {});
}

// CommonJS export so the file is consumable from Node without a build step,
// while staying dependency-free and framework-agnostic.
module.exports = { convertList, convertListDefault, resolveSep, SEP_CHAR };

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →