Wednesday, July 15, 2026 3:56:41 AM
> hash_map.rb
# frozen_string_literal: true

module ESM
  module Arma
    class HashMap < ActiveSupport::HashWithIndifferentAccess
      # @param input [String, Array, Hash, OpenStruct] The data to be converted. If a String, data must be array pairs
      def self.from(input)
        hash_map = new
        return hash_map if input.blank?

        hash_map.from(input)
      rescue
        nil
      end

      def initialize(data = {})
        super
        return if data.blank?

        from(data)
      end

      def from(input)
        hash = normalize(input)
        merge!(hash)

        self
      end

      def to_a
        convert_value =
          lambda do |value|
            case value
            when Array
              value.map { |v| convert_value.call(v) }
            when Hash, ActiveSupport::HashWithIndifferentAccess
              value.map do |key, value|
                [convert_value.call(key), convert_value.call(value)]
              end
            when Symbol
              value.to_s
            else
              value
            end
          end

        map { |key, value| [key, convert_value.call(value)] }
      end

      def to_json(*)
        ::JSON.generate(to_a, *)
      end

      alias_method :to_s, :to_json

      delegate :to_ostruct, :to_istruct, to: :to_h
      delegate_missing_to :to_h

      private

      # The parameters sent over by Arma can be in a SimpleArray format. This will convert the value if need be.
      # Parameters can be of type:
      def normalize(input)
        case input
        when OpenStruct, Struct, Hash, ActiveSupport::HashWithIndifferentAccess
          input = input.to_h
          return if input.nil?

          input.transform_keys { |k| normalize(k) }
          input.transform_values { |v| normalize(v) }
        when Array, String
          # This will attempt to parse a string for json
          possible_hash_map =
            if input.is_a?(String)
              input.gsub("\"\"", "\"") # Handle arma string escape
                .gsub("<br/>", "\\n") # Handle <br/>
                .gsub("<br />", "\\n") # Handle <br />
                .gsub("<br></br>", "\\n") # Handle <br></br>
                .parse_json
            else
              input
            end

          if valid_hash_structure?(possible_hash_map)
            possible_hash_map.each_with_object({}) do |(key, value), obj|
              obj[normalize(key)] = normalize(value)
            end
          elsif possible_hash_map.is_a?(Array)
            possible_hash_map.map { |i| normalize(i) }
          else
            input
          end
        when Symbol
          input.to_s
        else
          input
        end
      end

      # Checks if the array is set up to be able to be converted to a hash
      # The input must be an array and in the format of [[key, value], [key, value]]
      def valid_hash_structure?(input)
        input.is_a?(Array) &&
          input.all? do |i|
            i.is_a?(Array) &&
              i.size == 2 &&
              i.first.is_a?(String)
          end
      end
    end
  end
end
All opinions represented herein are my own
- © 2024 - 2026 itsthedevman
- build 43aa0d7