JSFiddle - React, Tailwind, and code Playground

by alexb

HTML

<!DOCTYPE html>
<title>JSON Selector</title>
<script src="https://unpkg.com/[email protected]/build.js"></script>
<div><tt>const obj =&nbsp;</tt><textarea>{
   "vals": {
      "more": [
         {
            "a":1,
            "b":2
         },
         {
            "a":3,
            "b":4
         }
      ]
   }
}</textarea></div>
<p><tt></tt></p>

CSS

div {
  display: flex;
}

textarea {
  flex-grow: 1;
  height: 300px;
}

JavaScript

function cursorCompare(a, b) {
  return a.line - b.line || a.column - b.column;
}

function cursorBetween(cursor, start, end) {
  return cursorCompare(cursor, start) >= 0 && cursorCompare(cursor, end) <= 0;
}

/**
 * @param {object} ast
 * @param {{ line: number, column: number }} cursor
 * @returns {string[]}
 */
function accessor(ast, cursor, path = []) {
  if (ast && ast.children) {
    for (const i in ast.children) {
      const child = ast.children[i];
      if (cursorBetween(cursor, child.loc.start, child.loc.end)) {
        const [key, value] = child.key
          ? [child.key.value, child.value]
          : [i, child];
        path.push(key);
        return accessor(value, cursor, path);
      }
    }
  }
  return path;
}

// Code below demonstrates use of `accessor` function.

const input = document.querySelector("textarea");
const p = document.querySelector("p tt");
let ast, cursorPos;

function updateAST() {
  try {
    ast = jsonToAst(input.value, { loc: true });
  } catch (e) {
    ast = null;
  }
}

function updateCursorPosition() {
  if (input.selectionStart !== cursorPos) {
    cursorPos = input.selectionStart;
    onCursorPositionChange();
  }
}

input.addEventListener("input", updateAST);
input.addEventListener("click", updateCursorPosition);
input.addEventListener("keyup", updateCursorPosition);

function onCursorPositionChange() {
	if (!ast) {
  p.textContent = 'Invalid JSON';
  	return;
  }
  const beforeCursor = input.value.substr(0, cursorPos);
  const lines = beforeCursor.split(/\r\n?|\n/);
  const cursor = {
    line: lines.length,
    column: lines[lines.length - 1].length + 1
  };
  const path = accessor(ast, cursor);
  const pathStr = path
    .map(p => (/^\d+$/.test(p) ? `[${p}]` : `['${p}']`))
    .join("");
  p.textContent = "obj" + pathStr;
}

updateAST();
updateCursorPosition();