@components-1812/json-visualizer
Version:
A web component for visualizing JSON data in a collapsible tree view.
231 lines (165 loc) • 7.04 kB
JavaScript
/**
* @typedef {Object} Token
* @property {'brace-open' | 'brace-close' | 'bracket-open' | 'bracket-close' | 'colon' | 'comma' | 'string' | 'number' | 'boolean' | 'null'} type
* The specific type of the token, representing different JSON syntax elements.
* @property {string | boolean | number | null} value
* The actual value of the token. This can be any data type.
* @property {Array<'brace' | 'bracket' | 'colon' | 'comma' | 'string' | 'number' | 'boolean' | 'null' | 'open' | 'close' | 'key' | 'value' | 'array-value'>} tags
* A list of additional tags that describe the token's role or category.
*/
export class JSONTokenizer {
static version = "0.0.2";
/**
* @type {Token[]}
*/
tokens = [];
constructor(){}
//MARK: tokenize
/**
*
* @param {String} rawJSON
* @returns
*/
tokenize(rawJson){
const minifyJson = this.clearJSON(rawJson);
this.tokens = [];
let contextStack = [];
let role = 'key';
let i = 0;
while(i < minifyJson.length){
const char = minifyJson[i];
const currentContext = contextStack.at(-1);
if(char === '{'){
contextStack.push('{');
this.tokens.push({ type: 'brace-open', value: char, tags: ['brace', 'open'] });
role = 'key';
i++; continue;
}
if(char === '}'){
contextStack.pop();
this.tokens.push({ type: 'brace-close', value: char, tags: ['brace', 'close'] });
i++; continue;
}
if(char === '['){
contextStack.push('[');
this.tokens.push({ type: 'bracket-open', value: char, tags: ['bracket', 'open'] });
role = 'array-value';
i++; continue;
}
if(char === ']'){
contextStack.pop();
this.tokens.push({ type: 'bracket-close', value: char, tags: ['bracket', 'close'] });
i++; continue;
}
if(char === ','){
this.tokens.push({ type: 'comma', value: char, tags: ['comma'] });
role = currentContext === '{' ? 'key' : 'array-value';
i++; continue;
}
if(char === ':'){
this.tokens.push({ type: 'colon', value: char, tags: ['colon'] });
role = 'value';
i++; continue;
}
//Detecta el inicio del string
if(char === '"'){
const { value, endIndex } = this._parseString(minifyJson, i);
this.tokens.push({ type: 'string-open', value: char, tags: ['string', 'open', role] });
this.tokens.push({ type: 'string', value, tags: ['string', role] });
this.tokens.push({ type: 'string-close', value: char, tags: ['string', 'close', role] });
i = endIndex + 1; continue;
}
//Boleanos
if(char === 't' || char === 'f'){
const { value, endIndex } = this._parseBoolean(minifyJson, i);
this.tokens.push({ type: 'boolean', value, tags: ['boolean', role, value ? 'true' : 'false'] });
i = endIndex + 1; continue;
}
//Null
if(char === 'n'){
const { value, endIndex } = this._parseNull(minifyJson, i);
this.tokens.push({ type: 'null', value, tags: ['null', role] });
i = endIndex + 1; continue;
}
//Numeros
if (/[0-9\-]/.test(char)) {
const { value, endIndex } = this._parseNumber(minifyJson, i);
this.tokens.push({ type: 'number', value, tags: ['number', role] });
i = endIndex + 1; continue;
}
}
}
//MARK: cleanJSON
/**
*
* @param {String} rawJSON
* @returns {String} A minified JSON string
*/
clearJSON(rawJson){
if(!rawJson){
console.warn(`No raw JSON provided to clearJSON method.`);
return;
}
const json = JSON.parse(rawJson);
const minifyJson = JSON.stringify(json);
return minifyJson;
}
//MARK: Parse Functions
/**
* @param {String} rawJson A raw json string
* @param {number} startIndex The index of the opening `"` character
* @returns {{value:string, raw:string, endIndex:number}} endIndex is the index of the `"` that closes the string
*/
_parseString(rawJson, startIndex){
let i = startIndex + 1;
while(i < rawJson.length){
//Omitimos los escapes de con \\
if(rawJson.at(i) === '\\'){
i += 2; continue;
}
//Detecta el final del string
if(rawJson.at(i) === '"') break;
i++;
}
const raw = rawJson.slice(startIndex, i + 1);
return { value: JSON.parse(raw), raw, endIndex: i };
}
/**
* @param {String} rawJson
* @param {number} startIndex The index of the first character of the number: `0-9` or `-`
* @returns {{value:number, endIndex:number}} endIndex is the index of the last character of the number
*/
_parseNumber(rawJson, startIndex){
let i = startIndex;
// Recorre mientras sea parte de un número
while(i < rawJson.length && /[0-9eE\.\+\-]/.test(rawJson[i])){
i++;
}
const raw = rawJson.slice(startIndex, i);
return { value: Number(raw), raw, endIndex: i - 1 };
}
/**
* @param {String} rawJson A raw json string
* @param {number} startIndex The start index of `t` or `f` of `true` or `false` to extract
* @returns {value:string, raw:string, endIndex:number} endIndex is the index of the last character of the token: `e`
*/
_parseBoolean(rawJson, startIndex){
if(rawJson.startsWith('true', startIndex)){
return { value: true, raw: 'true', endIndex: startIndex + 3 };
}
if(rawJson.startsWith('false', startIndex)){
return { value: false, raw: 'false', endIndex: startIndex + 4 };
}
}
/**
* @param {*} rawJson A raw json string
* @param {*} startIndex The start index of `n` of `null` to extract
* @returns {{value:null, raw:string, endIndex:number}} endIndex is the index of the last character of the token: `l`
*/
_parseNull(rawJson, startIndex){
if(rawJson.startsWith('null', startIndex)){
return { value: null, raw: 'null', endIndex: startIndex + 3 };
}
}
}
export default JSONTokenizer;