# The JSON module allows parsing and generating [JSON](http://json.org/) documents.
#
# NOTE: To use `JSON` or its children, you must explicitly import it with `require "json"`
#
# ### General type-safe interface
#
# The general type-safe interface for parsing JSON is to invoke `T.from_json` on a
# target type `T` and pass either a `String` or `IO` as an argument.
#
# ```
# require "json"
#
# json_text = %([1, 2, 3])
# Array(Int32).from_json(json_text) # => [1, 2, 3]
#
# json_text = %({"x": 1, "y": 2})
# Hash(String, Int32).from_json(json_text) # => {"x" => 1, "y" => 2}
# ```
#
# Serializing is achieved by invoking `to_json`, which returns a `String`, or
# `to_json(io : IO)`, which will stream the JSON to an `IO`.
#
# ```
# require "json"
#
# [1, 2, 3].to_json            # => "[1,2,3]"
# {"x" => 1, "y" => 2}.to_json # => "{\"x\":1,\"y\":2}"
# ```
#
# Most types in the standard library implement these methods. For user-defined types
# you can define a `self.new(pull : JSON::PullParser)` for parsing and
# `to_json(builder : JSON::Builder)` for serializing. The following sections
# show convenient ways to do this using `JSON::Serializable`.
#
# NOTE: JSON object keys are always strings but they can still be parsed
# and deserialized to other types. To deserialize, define a
# `T.from_json_object_key?(key : String) : T?` method, which can return `nil`
# if the string can't be parsed into that type. To serialize, define a
# `to_json_object_key : String` method can be serialized that way.
# All integer and float types in the standard library can be deserialized that way.
#
# ```
# require "json"
#
# json_text = %({"1": 2, "3": 4})
# Hash(Int32, Int32).from_json(json_text) # => {1 => 2, 3 => 4}
#
# {1.5 => 2}.to_json # => "{\"1.5\":2}"
# ```
#
# ### Parsing with `JSON.parse`
#
# `JSON.parse` will return an `Any`, which is a convenient wrapper around all possible JSON types,
# making it easy to traverse a complex JSON structure but requires some casts from time to time,
# mostly via some method invocations.
#
# ```
# require "json"
#
# value = JSON.parse("[1, 2, 3]") # : JSON::Any
#
# value[0]               # => 1
# typeof(value[0])       # => JSON::Any
# value[0].as_i          # => 1
# typeof(value[0].as_i)  # => Int32
# value[0].as_i?         # => 1
# typeof(value[0].as_i?) # => Int32 | Nil
# value[0].as_s?         # => nil
# typeof(value[0].as_s?) # => String | Nil
#
# value[0] + 1       # Error, because value[0] is JSON::Any
# value[0].as_i + 10 # => 11
# ```
#
# `JSON.parse` can read from an `IO` directly (such as a file) which saves
# allocating a string:
#
# ```
# require "json"
#
# json = File.open("path/to/file.json") do |file|
#   JSON.parse(file)
# end
# ```
#
# Parsing with `JSON.parse` is useful for dealing with a dynamic JSON structure.
#
# ### Generating with `JSON.build`
#
# Use `JSON.build`, which uses `JSON::Builder`, to generate JSON
# by emitting scalars, arrays and objects:
#
# ```
# require "json"
#
# string = JSON.build do |json|
#   json.object do
#     json.field "name", "foo"
#     json.field "values" do
#       json.array do
#         json.number 1
#         json.number 2
#         json.number 3
#       end
#     end
#   end
# end
# string # => %<{"name":"foo","values":[1,2,3]}>
# ```
#
# ### Generating with `to_json`
#
# `to_json`, `to_json(IO)` and `to_json(JSON::Builder)` methods are provided
# for primitive types, but you need to define `to_json(JSON::Builder)`
# for custom objects, either manually or using `JSON::Serializable`.
module JSON
  # Generic JSON error.
  class Error < Exception
  end

  # Exception thrown on a JSON parse error.
  class ParseException < Error
    @line_number : Int64
    @column_number : Int64

    def line_number : Int32
      @line_number.to_i32
    end

    def column_number : Int32
      @column_number.to_i32
    end

    @[Experimental]
    def line_number_i64 : Int64
      @line_number
    end

    @[Experimental]
    def column_number_i64
      @column_number
    end

    def initialize(message, line_number, column_number, cause = nil)
      @line_number = line_number.to_i64
      @column_number = column_number.to_i64
      super "#{message} at line #{@line_number}, column #{@column_number}", cause
    end

    def location : {Int32, Int32}
      {line_number, column_number}
    end

    @[Experimental]
    def location_i64 : {Int64, Int64}
      {line_number_i64, column_number_i64}
    end
  end

  # Parses a JSON document as a `JSON::Any`.
  def self.parse(input : String | IO) : Any
    Parser.new(input).parse
  end
end

require "./json/*"
