Module: JSON

Defined in:
lib/neatjson.rb

Class Method Summary collapse

Class Method Details

.neat_generate(object, opts = {}) ⇒ String

Generate the JSON string representation for an object, with a variety of formatting options.

The lambda for the sort option will be passed the string name of the key, the value, and the hash for the object being sorted. The values returned for all keys must be all comparable, or an error will occur.

Parameters:

  • object (Object)

    the object to serialize

  • opts (Hash) (defaults to: {})

    the formatting options

Options Hash (opts):

  • :wrap (Integer) — default: 80

    The maximum line width before wrapping. Use false to never wrap, or true to always wrap.

  • :indent (String) — default: " "

    Whitespace used to indent each level when wrapping (without the :short option).

  • :indent_last (Boolean) — default: false

    Indent the closing bracket for arrays and objects (without the :short option).

  • :short (Boolean) — default: false

    Keep the output 'short' when wrapping, putting opening brackets on the same line as the first value, and closing brackets on the same line as the last item.

  • :sort (Boolean) — default: false

    Sort the keys for objects to be in alphabetical order (true), or supply a lambda to determine ordering.

  • :aligned (Boolean) — default: false

    When wrapping objects, align the colons (only per object).

  • :force_floats (Boolean) — default: false

    Ensure that all integer values have .0 after them.

  • :force_floats_in (Array) — default: []

    Array of object key names to force floats inside of.

  • :decimals (Integer) — default: null

    Decimal precision to use for floats; omit to keep numeric values precise.

  • :trim_trailing_zeros (Boolean) — default: false

    Remove trailing zeros added by decimals` from the ends of floating point numbers.

  • :padding (Integer) — default: 0

    Number of spaces to put inside brackets/braces for both arrays and objects.

  • :array_padding (Integer) — default: 0

    Number of spaces to put inside brackets for arrays. Overrides :padding.

  • :object_padding (Integer) — default: 0

    Number of spaces to put inside braces for objects. Overrides :padding.

  • :around_comma (Integer) — default: 0

    Number of spaces to put before/after commas (for both arrays and objects).

  • :before_comma (Integer) — default: 0

    Number of spaces to put before commas (for both arrays and objects).

  • :after_comma (Integer) — default: 0

    Number of spaces to put after commas (for both arrays and objects).

  • :around_colon (Integer) — default: 0

    Number of spaces to put before/after colons (for objects).

  • :before_colon (Integer) — default: 0

    Number of spaces to put before colons (for objects).

  • :after_colon (Integer) — default: 0

    Number of spaces to put after colons (for objects).

  • :around_colon_1 (Integer) — default: 0

    Number of spaces to put before/after colons for single-line objects.

  • :before_colon_1 (Integer) — default: 0

    Number of spaces to put before colons for single-line objects.

  • :after_colon_1 (Integer) — default: 0

    Number of spaces to put after colons for single-line objects.

  • :around_colon_n (Integer) — default: 0

    Number of spaces to put before/after colons for multi-line objects.

  • :before_colon_n (Integer) — default: 0

    Number of spaces to put before colons for multi-line objects.

  • :after_colon_n (Integer) — default: 0

    Number of spaces to put after colons for multi-line objects.

Returns:

  • (String)

    the JSON representation of the object.

Author:



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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/neatjson.rb', line 38

def self.neat_generate(object,opts={})
  opts ||= {}
  opts[:wrap] = 80 unless opts.key?(:wrap)
  opts[:wrap] = -1 if opts[:wrap]==true
  opts[:indent]         ||= "  "
  opts[:force_floats_in]||= []
  opts[:array_padding]  ||= opts[:padding]      || 0
  opts[:object_padding] ||= opts[:padding]      || 0
  opts[:after_comma]    ||= opts[:around_comma] || 0
  opts[:before_comma]   ||= opts[:around_comma] || 0
  opts[:before_colon]   ||= opts[:around_colon] || 0
  opts[:after_colon]    ||= opts[:around_colon] || 0
  opts[:before_colon_1] ||= opts[:around_colon_1] || opts[:before_colon] || 0
  opts[:after_colon_1]  ||= opts[:around_colon_1] || opts[:after_colon]  || 0
  opts[:before_colon_n] ||= opts[:around_colon_n] || opts[:before_colon] || 0
  opts[:after_colon_n]  ||= opts[:around_colon_n] || opts[:after_colon]  || 0
  raise ":indent option must only be whitespace" if opts[:indent]=~/\S/

  apad  = " " * opts[:array_padding]
  opad  = " " * opts[:object_padding]
  comma = "#{' '*opts[:before_comma]},#{' '*opts[:after_comma]}"
  colon1= "#{' '*opts[:before_colon_1]}:#{' '*opts[:after_colon_1]}"
  colonn= "#{' '*opts[:before_colon_n]}:#{' '*opts[:after_colon_n]}"

  memoizer = {}
  build = ->(o,indent,floats_forced) do
    memoizer[[o,indent,floats_forced]] ||= case o
      when String               then "#{indent}#{self.generate(o)}"
      when Symbol               then "#{indent}#{self.generate(o.to_s)}"
      when TrueClass,FalseClass then "#{indent}#{o}"
      when NilClass             then "#{indent}null"
      when Integer
        floats_forced ? build[o.to_f, indent, floats_forced] : "#{indent}#{o.inspect}"
      when Float
        if o.infinite?
          "#{indent}#{o<0 ? "-9e9999" : "9e9999"}"
        elsif o.nan?
          "#{indent}\"NaN\""
        elsif !floats_forced && (o==o.to_i) && (o.to_s !~ /e/)
          build[o.to_i, indent, floats_forced]
        elsif opts[:decimals]
          if opts[:trim_trailing_zeros]
            "#{indent}#{o.round(opts[:decimals])}"
          else
            "#{indent}%.#{opts[:decimals]}f" % o
          end
        else
          "#{indent}#{o}"
        end

      when Array
        if o.empty?
          "#{indent}[]"
        else
          pieces = o.map{ |v| build[v, '', floats_forced] }
          one_line = "#{indent}[#{apad}#{pieces.join comma}#{apad}]"
          if !opts[:wrap] || (one_line.length <= opts[:wrap])
            one_line
          elsif opts[:short]
            indent2 = "#{indent} #{apad}"
            pieces = o.map{ |v| build[v, indent2, floats_forced] }
            pieces[0] = pieces[0].sub indent2, "#{indent}[#{apad}"
            pieces[pieces.length-1] = "#{pieces.last}#{apad}]"
            pieces.join ",\n"
          else
            indent2 = "#{indent}#{opts[:indent]}"
            "#{indent}[\n#{o.map{ |v| build[v, indent2, floats_forced] }.join ",\n"}\n#{opts[:indent_last] ? indent2 : indent}]"
          end
        end

      when Hash
        if o.empty?
          "#{indent}{}"
        else
          case sort=(opts[:sorted] || opts[:sort])
            when true then o = o.sort_by{|k,v| k.to_s }
            when Proc
              o = case sort.arity
              when 1 then o.sort_by{ |k,v| sort[k] }
              when 2 then o.sort_by{ |k,v| sort[k,v] }
              when 3 then o.sort_by{ |k,v| sort[k,v,o] }
              end
          end
          keys = o.map{ |x| x.first.to_s }
          keyvals = o.map.with_index{ |(k,v),i| [ self.generate(k.to_s), build[v, '', opts[:force_floats] || opts[:force_floats_in].include?(keys[i])] ] }
          keyvals = keyvals.map{ |kv| kv.join(colon1) }.join(comma)
          one_line = "#{indent}{#{opad}#{keyvals}#{opad}}"
          if !opts[:wrap] || (one_line.length <= opts[:wrap])
            one_line
          else
            if opts[:short]
              keyvals = o.map{ |k,v| ["#{indent} #{opad}#{self.generate(k.to_s)}",v] }
              keyvals[0][0] = keyvals[0][0].sub "#{indent} ", "#{indent}{"
              if opts[:aligned]
                longest = keyvals.map(&:first).map(&:length).max
                formatk = "%-#{longest}s"
                keyvals.map!{ |k,v| [ formatk % k,v] }
              end
              keyvals.map!.with_index do |(k,v),i|
                floats_forced = opts[:force_floats] || opts[:force_floats_in].include?(keys[i])
                indent2 = " "*"#{k}#{colonn}".length
                one_line = "#{k}#{colonn}#{build[v, '', floats_forced]}"
                if opts[:wrap] && (one_line.length > opts[:wrap]) && (v.is_a?(Array) || v.is_a?(Hash))
                  "#{k}#{colonn}#{build[v, indent2, floats_forced].lstrip}"
                else
                  one_line
                end
              end
              "#{keyvals.join(",\n")}#{opad}}"
            else
              keyvals = o.map{ |k,v| ["#{indent}#{opts[:indent]}#{self.generate(k.to_s)}",v] }
              if opts[:aligned]
                longest = keyvals.map(&:first).map(&:length).max
                formatk = "%-#{longest}s"
                keyvals.map!{ |k,v| [ formatk % k,v] }
              end
              indent2 = "#{indent}#{opts[:indent]}"
              keyvals.map!.with_index do |(k,v),i|
                floats_forced = opts[:force_floats] || opts[:force_floats_in].include?(keys[i])
                one_line = "#{k}#{colonn}#{build[v, '', floats_forced]}"
                if opts[:wrap] && (one_line.length > opts[:wrap]) && (v.is_a?(Array) || v.is_a?(Hash))
                  "#{k}#{colonn}#{build[v, indent2, floats_forced].lstrip}"
                else
                  one_line
                end
              end
              "#{indent}{\n#{keyvals.join(",\n")}\n#{opts[:indent_last] ? indent2 : indent}}"
            end
          end
        end

      else
        "#{indent}#{o.to_json(opts)}"
    end
  end

  build[object, '', opts[:force_floats]]
end