summaryrefslogtreecommitdiff
path: root/lib/ffi_yajl/encoder/ffi.rb
blob: 2e79577a48870dfaca60c7de5d35b6ddad3cdc69 (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

module FFI_Yajl
  class Encoder
    attr_accessor :opts

    def self.encode(obj, *args)
      new(*args).encode(obj)
    end

    def initialize(opts = {})
      @opts = opts
    end

    def encode(obj)
      yajl_gen = FFI_Yajl.yajl_gen_alloc(nil, nil)

      # configure the yajl encoder
      FFI_Yajl.yajl_gen_config(yajl_gen, :yajl_gen_beautify, :int, 1) if opts[:pretty]
      FFI_Yajl.yajl_gen_config(yajl_gen, :yajl_gen_validate_utf8, :int, 1)
      indent = if opts[:pretty]
                 opts[:indent] ? opts[:indent] : "  "
               else
                 " "
               end
      FFI_Yajl.yajl_gen_config(yajl_gen, :yajl_gen_indent_string, :string, indent)

      # setup our own state
      state = {
        :json_opts => opts,
        :processing_key => false,
      }

      # do the encoding
      obj.ffi_yajl(yajl_gen, state)

      # get back our encoded JSON
      string_ptr = FFI::MemoryPointer.new(:string)
      length_ptr = FFI::MemoryPointer.new(:int)
      FFI_Yajl.yajl_gen_get_buf(yajl_gen, string_ptr, length_ptr)
      length = length_ptr.read_int
      string = string_ptr.get_pointer(0).read_string

      return string
    ensure
      # free up the yajl encoder
      FFI_Yajl.yajl_gen_free(yajl_gen)
    end
  end

end


class Hash
  def ffi_yajl(yajl_gen, state)
    FFI_Yajl.yajl_gen_map_open(yajl_gen)
    self.each do |key, value|
      # Perf Fix: mutate state hash rather than creating new copy
      state[:processing_key] = true
      key.ffi_yajl(yajl_gen, state)
      state[:processing_key] = false
      value.ffi_yajl(yajl_gen, state)
    end
    FFI_Yajl.yajl_gen_map_close(yajl_gen)
  end
end

class Array
  def ffi_yajl(yajl_gen, state)
    FFI_Yajl.yajl_gen_array_open(yajl_gen)
    self.each do |value|
      value.ffi_yajl(yajl_gen, state)
    end
    FFI_Yajl.yajl_gen_array_close(yajl_gen)
  end
end

class NilClass
  def ffi_yajl(yajl_gen, state)
    FFI_Yajl.yajl_gen_null(yajl_gen)
  end
end

class TrueClass
  def ffi_yajl(yajl_gen, state)
    FFI_Yajl.yajl_gen_bool(yajl_gen, 0)
  end
end

class FalseClass
  def ffi_yajl(yajl_gen, state)
    FFI_Yajl.yajl_gen_bool(yajl_gen, 1)
  end
end

class Fixnum
  def ffi_yajl(yajl_gen, state)
    if state[:processing_key]
      str = self.to_s
      FFI_Yajl.yajl_gen_string(yajl_gen, str, str.length)
    else
      FFI_Yajl.yajl_gen_integer(yajl_gen, self)
    end
  end
end

class Bignum
  def ffi_yajl(yajl_gen, state)
    raise NotImpelementedError
  end
end

class Float
  def ffi_yajl(yajl_gen, state)
    FFI_Yajl.yajl_gen_double(yajl_gen, self)
  end
end

class String
  def ffi_yajl(yajl_gen, state)
    FFI_Yajl.yajl_gen_string(yajl_gen, self, self.length)
  end
end

# I feel dirty
class Object
  unless defined?(ActiveSupport)
    def to_json(*args, &block)
      "\"#{to_s}\""
    end
  end

  def ffi_yajl(yajl_gen, state)
    json = self.to_json(state[:json_opts])
    FFI_Yajl.yajl_gen_number(yajl_gen, json, json.length)
  end
end