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).

  • :decimals (Integer) — default: null

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

  • :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.

  • :aroun_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:



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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/neatjson.rb', line 35

def self.neat_generate(object,opts={})
	opts ||= {}
	opts[:wrap] = 80 unless opts.key?(:wrap)
	opts[:wrap] = -1 if opts[:wrap]==true
	opts[:indent]         ||= "  "
	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) do
		memoizer[[o,indent]] ||= case o
			when String,Integer       then "#{indent}#{o.inspect}"
			when Symbol               then "#{indent}#{o.to_s.inspect}"
			when TrueClass,FalseClass then "#{indent}#{o}"
			when NilClass             then "#{indent}null"
			when Float
				if o.infinite?
					"#{indent}#{o<0 ? "-9e9999" : "9e9999"}"
				elsif o.nan?
					"#{indent}\"NaN\""
				elsif (o==o.to_i) && (o.to_s !~ /e/)
					build[o.to_i,indent]
				elsif opts[:decimals]
					"#{indent}%.#{opts[:decimals]}f" % o
				else
					"#{indent}#{o}"
				end

			when Array
				if o.empty?
					"#{indent}[]"
				else
					pieces = o.map{ |v| build[v,''] }
					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 ] }
						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 ] }.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
					keyvals = o.map{ |k,v| [ k.to_s.inspect, build[v,''] ] }
					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}#{k.to_s.inspect}",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! do |k,v|
								indent2 = " "*"#{k}#{colonn}".length
								one_line = "#{k}#{colonn}#{build[v,'']}"
								if opts[:wrap] && (one_line.length > opts[:wrap]) && (v.is_a?(Array) || v.is_a?(Hash))
									"#{k}#{colonn}#{build[v,indent2].lstrip}"
								else
									one_line
								end
							end
							"#{keyvals.join(",\n")}#{opad}}"
						else
							keyvals = o.map{ |k,v| ["#{indent}#{opts[:indent]}#{k.to_s.inspect}",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! do |k,v|
								one_line = "#{k}#{colonn}#{build[v,'']}"
								if opts[:wrap] && (one_line.length > opts[:wrap]) && (v.is_a?(Array) || v.is_a?(Hash))
									"#{k}#{colonn}#{build[v,indent2].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,'']
end