Позиция в строке JSON для пути в объекте

У меня есть строка JSON, подобная этой:

{
    "Version": "XXX",
    "Statements": [
        {...},
        {...},
        {...}
    ]
}

Как узнать, какой объект внутри свойства Statements определен в символе XX строки JSON? (учитывая, что эти объекты могут иметь произвольно глубокое вложение).

Например, если у меня есть строка

{"Version":"XXX","Statements":[{"a":1},{"b":2},{"b":3}]}
--------------------------------------------------------
123456789 123456789 123456789 123456789 123456789 123456

то символ в позиции 36 будет соответствовать первому объекту оператора, тогда как символ в позиции 52 будет соответствовать третьему объекту оператора.

Ответ 1

После нескольких исследований я думаю, что у меня есть путь вперед, не написав собственный парсер, используя пакет esprima. Поскольку esprima это не JSON специфический (но скорее JavaScript), я должен обернуть свою строку JSON в скобки.

Каждый элемент дерева содержит свойство loc с диапазоном, соответствующим ему, в позицию в исходной строке JSON.

var esprima = require("esprima");
var JSONPath = require('JSONPath');

function getStatementIndex(str, line, column) {
	var tree = esprima.parseScript(str, {loc:true});
	var query = "$.body[0].expression.properties[?(@.key.value=='Statement')].value.elements[*].loc";
	var locations = JSONPath({json: tree, path: query});
	
	console.log(locations);
	
	for(var i = 0; i < locations.length; i++) {
		var loc = locations[i];
		
		var contains = false;
		
		if (loc.start.line < line && loc.end.line > line) {
			continue;
		}
		
		// If a single line and in between
		if (loc.start.line == loc.end.line && loc.start.line == line) {
			if (loc.start.column <= column && loc.end.column >= column) {
				contains = true;
			}
			
		// If on the beginning line
		} else if (loc.start.line == line && loc.start.column <= column) {
			contains = true;
		
		// If on the end line
		} else if (loc.end.line == line && loc.end.column >= column) {
			contains = true;
		
		// If in between
		} else if (loc.start.line < line  && loc.end.line > line) {
			contains = true;
		}
					
		if (contains)
			return i;
	}
	
	return -1;
}

var result = getStatementIndex(str, 81, 7);

Ответ 2

Вот какое-то грязное решение, которое не требует внешних библиотек:

const data = '{"Version":"XXX","Statements":[{"a":1},{"b":2},{"b":3}],"some":0}';

const getValuesPositionInArray = arrayKey => data => {
  const arrayNameSeparator = '"${arrayKey}":';
  const targetArrayIndexOf = data.indexOf(arrayNameSeparator) + arrayNameSeparator.length;
  const arrayStringWithRest = data.slice(targetArrayIndexOf, data.length);
  
  const { result } = arrayStringWithRest.split('').reduce(
    (acc, char, idx, array) => {
      if (acc.finished) return acc;
      if (!acc.processingKey && char === '[') acc.nesting += 1;
      if (!acc.processingKey && char === ']') acc.nesting -= 1;
      
      const shouldFinish = acc.nesting === 0;
      const charIsDblQuote = char === '"';
      const charBefore = array[idx - 1];
      const charAfter = array[idx + 1];
      
      acc.position += 1;
      acc.finished = shouldFinish;

      if (acc.processingKey && !charIsDblQuote) acc.processedKey += char;
      if (charIsDblQuote) acc.processingKey = !acc.processingKey;
      if (charIsDblQuote && !acc.processingKey && charAfter === ':') {
      	acc.result[acc.processedKey] = acc.position;
        acc.processedKey = '';
      }
      
      return acc;
    }, 
    { 
      finished: false, 
      processingKey: false,
      processedKey: '',
      nesting: 0,
      position: targetArrayIndexOf + 1,
      result: {}
    }
  )
  
  return result;
  
}

const result = getValuesPositionInArray('Statements')(data);

console.log(result)

Ответ 3

Чтобы найти положение чего-то в строке json, если вы хотите создать свой собственный алгоритм, есть несколько вещей, которые нужно учитывать, одна из проблем заключается в том, что несколько строк могут привести к одному и тому же объекту литерала, а также порядок свойств в объекты не гарантируются, то такая же строка может привести к разному порядку в свойствах. Мы знаем, что каждый . означает { в строке, но [ может означать [ или {. Итак, чтобы найти позицию 1 например, мы должны удалить пробелы в исходной строке и выполнить рекурсивные циклы и снова построить json и найти совпадение. Вот только пример, чтобы найти позицию 1:

var json = '{"Version":"XXX","Statements":[{"a":1},{"b":2},{"b":3}]}';

var obj = JSON.parse(json)

var str2 = ""

for(p in obj){
    str2 += "{";
    str2 += p+":";
    if(p == "Statements"){
        str2 += ":["
        obj[p].forEach(o=>{            
            for(p2 in o){
                if(p2 == "a"){
                    str2 += '{"a":'
                }
            }
        })
    }else{
        str2 +='"'+obj[p]+'",'
    }    
}
console.log(str2)
console.log(str2.length+1)