Repository: puma/puma
File: lib/puma/json.rb (Ruby)

Code before:
# frozen_string_literal: true

module Puma

  # Puma deliberately avoids the use of the json gem and instead performs JSON
  # serialization without any external dependencies. In a puma cluster, loading
  # any gem into the puma master process means that operators cannot use a
  # phased restart to upgrade their application if the new version of that
  # application uses a different version of that gem. The json gem in
  # particular is additionally problematic because it leverages native
  # extensions. If the puma master process relies on a gem with native
  # extensions and operators remove gems from disk related to old releases,
  # subsequent phased restarts can fail.
  #
  # The implementation of JSON serialization in this module is not designed to
  # be particularly full-featured or fast. It just has to handle the few places
  # where Puma relies on JSON serialization internally.

  module JSON
    class SerializationError < StandardError; end

    class << self
      def generate(value)
        case value
        when Array, Hash
          parts = []
          serialize_value parts, value
          parts.join ''
        else
          raise SerializationError, "Could not serialize object of type #{value.class}"
        end
      end

      private

      def serialize_value(output, value)
        case value
        when true
          output << 'true'
        when false
          output << 'false'
        when Array
          output << '['
          value.each_with_index do |member, index|
            output << ',' if index != 0
            serialize_value output, member
          end
          output << ']'
        when Hash
          output << '{'
          value.each_with_index do |(k, v), index|

Review comment (performance):
Rather than an array, should it use [StringIO](https://ruby-doc.org/stdlib-2.5.1/libdoc/stringio/rdoc/StringIO.html)? Not sure if there is a (performance) benefit for one or the other.

Code after:
# frozen_string_literal: true
require 'stringio'

module Puma

  # Puma deliberately avoids the use of the json gem and instead performs JSON
  # serialization without any external dependencies. In a puma cluster, loading
  # any gem into the puma master process means that operators cannot use a
  # phased restart to upgrade their application if the new version of that
  # application uses a different version of that gem. The json gem in
  # particular is additionally problematic because it leverages native
  # extensions. If the puma master process relies on a gem with native
  # extensions and operators remove gems from disk related to old releases,
  # subsequent phased restarts can fail.
  #
  # The implementation of JSON serialization in this module is not designed to
  # be particularly full-featured or fast. It just has to handle the few places
  # where Puma relies on JSON serialization internally.

  module JSON
    QUOTE = /"/
    BACKSLASH = /\\/
    CONTROL_CHAR_TO_ESCAPE = /[\x00-\x1F]/ # As required by ECMA-404
    CHAR_TO_ESCAPE = Regexp.union QUOTE, BACKSLASH, CONTROL_CHAR_TO_ESCAPE

    class SerializationError < StandardError; end

    class << self
      def generate(value)
        StringIO.open do |io|
          serialize_value io, value
          io.string
        end
      end

      private

      def serialize_value(output, value)
        case value
        when Hash
          output << '{'
          value.each_with_index do |(k, v), index|
            output << ',' if index != 0
            serialize_object_key output, k
            output << ':'
            serialize_value output, v
          end
          output << '}'
        when Array
          output << '['
          value.each_with_index do |member, index|