# frozen_string_literal: true module Fiken # A lightweight, read-only wrapper around a JSON response body. # # Gives both dot-access and hash-access, built recursively: # # invoice = Fiken::Object.new("invoiceNumber" => 5, "lines" => [{ "vat" => 25 }]) # invoice.invoice_number # => 5 (also: invoice.invoiceNumber) # invoice[:invoiceNumber] # => 5 # invoice.lines.first.vat # => 25 class Object def initialize(attributes = {}) @attributes = {} (attributes || {}).each { |key, value| @attributes[key.to_sym] = wrap(value) } end def [](key) @attributes[normalize(key)] end def key?(key) @attributes.key?(normalize(key)) end def keys @attributes.keys end def to_h @attributes.transform_values { |value| unwrap(value) } end alias to_hash to_h def ==(other) other.is_a?(Object) && other.to_h == to_h end def respond_to_missing?(name, include_private = false) @attributes.key?(normalize(name)) || super end def method_missing(name, *args) key = normalize(name) return @attributes[key] if @attributes.key?(key) super end def inspect "#" end private # Accepts both :invoiceNumber and :invoice_number, mapping snake_case to the # camelCase keys Fiken returns. def normalize(key) sym = key.to_sym return sym if @attributes.key?(sym) camel = key.to_s.gsub(/_([a-z])/) { Regexp.last_match(1).upcase }.to_sym @attributes.key?(camel) ? camel : sym end def wrap(value) case value when Hash then Object.new(value) when Array then value.map { |element| wrap(element) } else value end end def unwrap(value) case value when Object then value.to_h when Array then value.map { |element| unwrap(element) } else value end end end end