Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Position in JSON string to path in the object

I have a JSON string similar to this one:

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

How can I find out which object inside Statements property is defined at character XX of the JSON string? (considering that those objects can have arbitrarily deep nesting).

For example, if I have a string

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

then character at position 36 would correspond to the first statement object, while character at position 52 would correspond to the third statement object.

like image 976
Maxim Avatar asked Sep 01 '18 02:09

Maxim


Video Answer


1 Answers

Here is some dirty solution that doesn't require external libs:

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)

But this snippet will break if target objects would contain string values.

EDIT

And below is updated snippet with string values fix and with parsed values as well:

const data = '{"Version":"XXX","Statements":[{"aa":"some"},{"b":"ano:,{ther}"},{"bb":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 charsAfterValue = ['}', ','];
  const charsBeforeKey = ['{', ','];

  const { result } = arrayStringWithRest.split('').reduce(
    (acc, char, idx, array) => {
      if (acc.finished) return acc;
      if (!acc.processingKey && !acc.processingValue && char === '[') acc.nesting += 1;
      if (!acc.processingKey && !acc.processingValue && char === ']') acc.nesting -= 1;

      const shouldFinish = acc.nesting === 0;
      const charIsDblQuote = char === '"';
      const charBefore = array[idx - 1];
      const charAfter = array[idx + 1];
     
      const keyProcessingStarted = (
        charIsDblQuote &&
        !acc.processingKey &&
        !acc.processingValue &&
        charsBeforeKey.includes(charBefore)
      );

      const keyProcessingFinished = (
        charAfter === ':' &&
        charIsDblQuote && 
        acc.processingKey 
      );

      const valueProcessingStarted = (
        char === ':' &&
        !acc.processingKey &&
        !acc.processingValue
      );

      const valueProcessingFinished = (
        (acc.lastProcessedValueType === String
          ? charIsDblQuote
          : true
        ) &&
        acc.processingValue &&
        charsAfterValue.includes(charAfter)
      );

      acc.position += 1;
      acc.finished = shouldFinish;

      if (acc.processingKey && !charIsDblQuote) acc.processedKey += char;
      if (acc.processingValue && !charIsDblQuote) acc.processedValue += char;
      
      if (keyProcessingStarted) {
        acc.processingKey = true;
      } else if (keyProcessingFinished) {
        acc.processingKey = false;
        acc.result[acc.processedKey] = { position: acc.position };
        acc.lastProcessedKey = acc.processedKey;
        acc.processedKey = '';
      }

      if (valueProcessingStarted) {
        acc.processingValue = true;
        acc.lastProcessedValueType = charAfter === '"' ? String : Number;
      } else if (valueProcessingFinished) {
        acc.processingValue = false;
      	acc.result[acc.lastProcessedKey].value = (
          acc.lastProcessedValueType(acc.processedValue)
        );
        acc.processedValue = '';
        acc.lastProcessedKey = '';
        acc.lastProcessedValueType = (v) => v;
      }

      return acc;
    },
    {
      finished: false,
      processingKey: false,
      processingValue: false,
      processedKey: '',
      processedValue: '',
      lastProcessedKey: '',
      lastProcessedValueType: (v) => v,
      nesting: 0,
      position: targetArrayIndexOf + 1,
      result: {}
    }
  )

  return result;

}

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

console.log(result)
like image 58
streletss Avatar answered Oct 14 '22 01:10

streletss