class JSON::Pure::Parser

This class implements the JSON parser that is used to parse a JSON string into a Ruby data structure.

Constants

ARRAY_CLOSE
ARRAY_OPEN
COLLECTION_DELIMITER
FALSE
FLOAT
IGNORE
INFINITY
INTEGER
MINUS_INFINITY
NAN
NULL
OBJECT_CLOSE
OBJECT_OPEN
PAIR_DELIMITER
STRING
STR_UMINUS
TRUE
UNESCAPE_MAP

Unescape characters in strings.

UNPARSED

Public Class Methods

new(source, opts = nil) click to toggle source

Creates a new JSON::Pure::Parser instance for the string source.

It will be configured by the opts hash. opts can have the following keys:

  • max_nesting: The maximum depth of nesting allowed in the parsed data structures. Disable depth checking with :max_nesting => false|nil|0, it defaults to 100.

  • allow_nan: If set to true, allow NaN, Infinity and -Infinity in defiance of RFC 7159 to be parsed by the Parser. This option defaults to false.

  • freeze: If set to true, all parsed objects will be frozen. Parsed string will be deduplicated if possible.

  • symbolize_names: If set to true, returns symbols for the names (keys) in a JSON object. Otherwise strings are returned, which is also the default. It’s not possible to use this option in conjunction with the create_additions option.

  • create_additions: If set to true, the Parser creates additions when a matching class and create_id are found. This option defaults to false.

  • object_class: Defaults to Hash. If another type is provided, it will be used instead of Hash to represent JSON objects. The type must respond to new without arguments, and return an object that respond to []=.

  • array_class: Defaults to Array If another type is provided, it will be used instead of Hash to represent JSON arrays. The type must respond to new without arguments, and return an object that respond to +<<+.

  • decimal_class: Specifies which class to use instead of the default

    (Float) when parsing decimal numbers. This class must accept a single
    string argument in its constructor.
Calls superclass method
# File lib/json/pure/parser.rb, line 79
def initialize(source, opts = nil)
  opts ||= {}
  source = convert_encoding source
  super source
  if !opts.key?(:max_nesting) # defaults to 100
    @max_nesting = 100
  elsif opts[:max_nesting]
    @max_nesting = opts[:max_nesting]
  else
    @max_nesting = 0
  end
  @allow_nan = !!opts[:allow_nan]
  @symbolize_names = !!opts[:symbolize_names]
  @freeze = !!opts[:freeze]
  if opts.key?(:create_additions)
    @create_additions = !!opts[:create_additions]
  else
    @create_additions = false
  end
  @symbolize_names && @create_additions and raise ArgumentError,
    'options :symbolize_names and :create_additions cannot be used '\
    'in conjunction'
  @create_id = @create_additions ? JSON.create_id : nil
  @object_class = opts[:object_class] || Hash
  @array_class  = opts[:array_class] || Array
  @decimal_class = opts[:decimal_class]
  @match_string = opts[:match_string]
end

Public Instance Methods

parse() click to toggle source

Parses the current JSON string source and returns the complete data structure as a result.

# File lib/json/pure/parser.rb, line 117
def parse
  reset
  obj = nil
  while !eos? && skip(IGNORE) do end
  if eos?
    raise ParserError, "source is not valid JSON!"
  else
    obj = parse_value
    UNPARSED.equal?(obj) and raise ParserError,
      "source is not valid JSON!"
    obj.freeze if @freeze
  end
  while !eos? && skip(IGNORE) do end
  eos? or raise ParserError, "source is not valid JSON!"
  obj
end
reset() click to toggle source
Calls superclass method
# File lib/json/pure/parser.rb, line 110
def reset
  super
  @current_nesting = 0
end

Private Instance Methods

convert_encoding(source) click to toggle source
# File lib/json/pure/parser.rb, line 136
def convert_encoding(source)
  if source.respond_to?(:to_str)
    source = source.to_str
  else
    raise TypeError,
      "#{source.inspect} is not like a string"
  end
  if source.encoding != ::Encoding::ASCII_8BIT
    source = source.encode(::Encoding::UTF_8)
    source.force_encoding(::Encoding::ASCII_8BIT)
  end
  source
end
parse_array() click to toggle source
# File lib/json/pure/parser.rb, line 248
def parse_array
  raise NestingError, "nesting of #@current_nesting is too deep" if
    @max_nesting.nonzero? && @current_nesting > @max_nesting
  result = @array_class.new
  delim = false
  loop do
    case
    when eos?
      raise ParserError, "unexpected end of string while parsing array"
    when !UNPARSED.equal?(value = parse_value)
      delim = false
      result << value
      skip(IGNORE)
      if scan(COLLECTION_DELIMITER)
        delim = true
      elsif match?(ARRAY_CLOSE)
        ;
      else
        raise ParserError, "expected ',' or ']' in array at '#{peek(20)}'!"
      end
    when scan(ARRAY_CLOSE)
      if delim
        raise ParserError, "expected next element in array at '#{peek(20)}'!"
      end
      break
    when skip(IGNORE)
      ;
    else
      raise ParserError, "unexpected token in array at '#{peek(20)}'!"
    end
  end
  result
end
parse_object() click to toggle source
# File lib/json/pure/parser.rb, line 282
def parse_object
  raise NestingError, "nesting of #@current_nesting is too deep" if
    @max_nesting.nonzero? && @current_nesting > @max_nesting
  result = @object_class.new
  delim = false
  loop do
    case
    when eos?
      raise ParserError, "unexpected end of string while parsing object"
    when !UNPARSED.equal?(string = parse_string)
      skip(IGNORE)
      unless scan(PAIR_DELIMITER)
        raise ParserError, "expected ':' in object at '#{peek(20)}'!"
      end
      skip(IGNORE)
      unless UNPARSED.equal?(value = parse_value)
        result[@symbolize_names ? string.to_sym : string] = value
        delim = false
        skip(IGNORE)
        if scan(COLLECTION_DELIMITER)
          delim = true
        elsif match?(OBJECT_CLOSE)
          ;
        else
          raise ParserError, "expected ',' or '}' in object at '#{peek(20)}'!"
        end
      else
        raise ParserError, "expected value in object at '#{peek(20)}'!"
      end
    when scan(OBJECT_CLOSE)
      if delim
        raise ParserError, "expected next name, value pair in object at '#{peek(20)}'!"
      end
      if @create_additions and klassname = result[@create_id]
        klass = JSON.deep_const_get klassname
        break unless klass and klass.json_creatable?
        result = klass.json_create(result)
      end
      break
    when skip(IGNORE)
      ;
    else
      raise ParserError, "unexpected token in object at '#{peek(20)}'!"
    end
  end
  result
end
parse_string() click to toggle source
# File lib/json/pure/parser.rb, line 164
def parse_string
  if scan(STRING)
    return '' if self[1].empty?
    string = self[1].gsub(%r{(?:\\[\\bfnrt"/]|(?:\\u(?:[A-Fa-f\d]{4}))+|\\[\x20-\xff])}n) do |c|
      k = $&[1]
      if u = UNESCAPE_MAP.fetch(k) { k.chr }
        u
      else # \uXXXX
        bytes = ''.b
        i = 0
        while c[6 * i] == ?\\ && c[6 * i + 1] == ?u
          bytes << c[6 * i + 2, 2].to_i(16) << c[6 * i + 4, 2].to_i(16)
          i += 1
        end
        bytes.encode(Encoding::UTF_8, Encoding::UTF_16BE).force_encoding(::Encoding::BINARY)
      end
    end
    string.force_encoding(::Encoding::UTF_8)

    if @freeze
      if STR_UMINUS
        string = -string
      else
        string.freeze
      end
    end

    if @create_additions and @match_string
      for (regexp, klass) in @match_string
        klass.json_creatable? or next
        string =~ regexp and return klass.json_create(string)
      end
    end
    string
  else
    UNPARSED
  end
rescue => e
  raise ParserError, "Caught #{e.class} at '#{peek(20)}': #{e}"
end
parse_value() click to toggle source
# File lib/json/pure/parser.rb, line 205
def parse_value
  case
  when scan(FLOAT)
    if @decimal_class then
      if @decimal_class == BigDecimal then
        BigDecimal(self[1])
      else
        @decimal_class.new(self[1]) || Float(self[1])
      end
    else
      Float(self[1])
    end
  when scan(INTEGER)
    Integer(self[1])
  when scan(TRUE)
    true
  when scan(FALSE)
    false
  when scan(NULL)
    nil
  when !UNPARSED.equal?(string = parse_string)
    string
  when scan(ARRAY_OPEN)
    @current_nesting += 1
    ary = parse_array
    @current_nesting -= 1
    ary
  when scan(OBJECT_OPEN)
    @current_nesting += 1
    obj = parse_object
    @current_nesting -= 1
    obj
  when @allow_nan && scan(NAN)
    NaN
  when @allow_nan && scan(INFINITY)
    Infinity
  when @allow_nan && scan(MINUS_INFINITY)
    MinusInfinity
  else
    UNPARSED
  end
end