Class: JsonMend::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/json_mend/parser.rb

Overview

The core parser that does the heavy lifting of fixing the JSON

Constant Summary collapse

MAX_ALLOWED_DEPTH =
100
COMMENT_DELIMETERS =
['#', '/'].freeze
NUMBER_CHARS =
Set.new('0123456789-.eE/,_'.chars).freeze
STRING_DELIMITERS =
['"', "'", '', ''].freeze
SKIP_CHARS_REGEX_CACHE =
{
  '"' => /"/,
  "'" => /'/,
  '' => //,
  '' => //,
  ':' => /:/,
  '}' => /\}/
}.freeze
ESCAPE_MAPPING =
{
  't' => "\t",
  'n' => "\n",
  'r' => "\r",
  'b' => "\b",
  'f' => "\f"
}.freeze
JSON_STOP_TOKEN =
:json_mend_stop_token
TERMINATORS_ARRAY =

Optimized constants for performance (CollectionLiteralInLoop)

[']', '}'].freeze
TERMINATORS_OBJECT_KEY =
[':', '}'].freeze
TERMINATORS_OBJECT_VALUE =
[',', '}'].freeze
TERMINATORS_ARRAY_ITEM =
[',', ']'].freeze
TERMINATORS_STRING_GUESSED =
['{', '}', '[', ']', ':', ','].freeze
TERMINATORS_VALUE =
[',', ']', '}'].freeze
STRING_OR_OBJECT_START =
(STRING_DELIMITERS + ['{', '[']).freeze
SKIPPED_KEYS =
%i[merged_array stray_colon].freeze
BOOLEAN_OR_NULL_CHARS =
%w[t f n].freeze
ESCAPE_START_CHARS =
%w[t n r b \\].freeze
HEX_ESCAPE_PREFIXES =
%w[u x].freeze
INVALID_NUMBER_TRAILERS =
['-', 'e', 'E', ','].freeze
NUMBER_REGEX =

Pre-compile regexes for performance

/[#{Regexp.escape(NUMBER_CHARS.to_a.join)}]+/
NUMBER_NO_COMMA_REGEX =
/[#{Regexp.escape(NUMBER_CHARS.dup.tap { |s| s.delete(',') }.to_a.join)}]+/

Instance Method Summary collapse

Constructor Details

#initialize(json_string) ⇒ Parser

Returns a new instance of Parser.



48
49
50
51
52
# File 'lib/json_mend/parser.rb', line 48

def initialize(json_string)
  @scanner = StringScanner.new(json_string)
  @context = []
  @depth = 0
end

Instance Method Details

#parseObject

Kicks off the parsing process. This is a direct port of the robust Python logic



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/json_mend/parser.rb', line 55

def parse
  json = parse_json

  # If the first parse returns JSON_STOP_TOKEN, it means we found nothing (empty string or garbage)
  # Return nil (or empty string representation logic elsewhere handles it)
  return nil if json == JSON_STOP_TOKEN

  unless @scanner.eos?
    json = [json]
    until @scanner.eos?
      new_json = parse_json
      if new_json == ''
        @scanner.getch # continue
      elsif new_json == JSON_STOP_TOKEN
        # Found nothing but EOS or garbage terminator
        break
      else
        # Ignore strings that look like closing braces garbage (e.g. "}", " ] ")
        next if new_json.is_a?(String) && new_json.strip.match?(/^[}\]]+$/)

        if both_hash?(json.last, new_json)
          json[-1] = deep_merge_hashes(json.last, new_json)
        else
          json << new_json
        end
      end
    end

    json = json.first if json.length == 1
  end

  json
end