aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ievkit/serializer.rb
blob: a0475cee545419f104d37feccc9c3d32c6ea432c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
require 'date'
require 'time'

module Ievkit
  class Serializer
    def self.any_json
      yajl || multi_json || json
    end

    def self.yajl
      require 'yajl'
      new(Yajl)
    rescue LoadError
    end

    def self.json
      require 'json'
      new(JSON)
    rescue LoadError
    end

    def self.multi_json
      require 'multi_json'
      new(MultiJson)
    rescue LoadError
    end

    def self.message_pack
      require 'msgpack'
      new(MessagePack, :pack, :unpack)
    rescue LoadError
    end

    def self.multipart
      new(IevMultipart)
    rescue LoadError
    end

    class IevMultipart
      def self.dump(data)
        data
      end
      
      def self.load(data)
        data
      end
    end

    # Public: Wraps a serialization format for Sawyer.  Nested objects are
    # prepared for serialization (such as changing Times to ISO 8601 Strings).
    # Any serialization format that responds to #dump and #load will work.
    def initialize(format, dump_method_name = nil, load_method_name = nil)
      @format = format
      @dump = @format.method(dump_method_name || :dump)
      @load = @format.method(load_method_name || :load)
    end

    # Public: Encodes an Object (usually a Hash or Array of Hashes).
    #
    # data - Object to be encoded.
    #
    # Returns an encoded String.
    def encode(data)
      data #@dump.call(encode_object(data))
    end
    alias dump encode

    # Public: Decodes a String into an Object (usually a Hash or Array of
    # Hashes).
    #
    # data - An encoded String.
    #
    # Returns a decoded Object.
    def decode(data)
      return nil if data.nil? || data.strip.empty?
      decode_object(@load.call(data))
    end

    alias load decode

    def encode_object(data)
      case data
      when Hash then encode_hash(data)
      when Array then data.map { |o| encode_object(o) }
      else data
      end
    end

    def encode_hash(hash)
      hash.keys.each do |key|
        case value = hash[key]
        when Date then hash[key] = value.to_time.utc.xmlschema
        when Time then hash[key] = value.utc.xmlschema
        when Hash then hash[key] = encode_hash(value)
        end
      end
      hash
    end

    def decode_object(data)
      case data
      when Hash then decode_hash(data)
      when Array then data.map { |o| decode_object(o) }
      else data
      end
    end

    def decode_hash(hash)
      hash.keys.each do |key|
        hash[key.to_sym] = decode_hash_value(key, hash.delete(key))
      end
      hash
    end

    def decode_hash_value(key, value)
      if time_field?(key, value)
        if value.is_a?(String)
          begin
            Time.parse(value)
          rescue ArgumentError
            value
          end
        elsif value.is_a?(Integer) || value.is_a?(Float)
          Time.at(value)          
        else
          value
        end
      elsif value.is_a?(Hash)
        decode_hash(value)
      elsif value.is_a?(Array)
        value.map { |o| decode_hash_value(key, o) }
      else
        value
      end
    end

    def time_field?(key, value)
      value && (key =~ /_(at|on)\z/ || key =~ /(\A|_)date\z/)
    end
  end
end