diff options
Diffstat (limited to 'lib/gitlab/utils/json_size_estimator.rb')
-rw-r--r-- | lib/gitlab/utils/json_size_estimator.rb | 104 |
1 files changed, 104 insertions, 0 deletions
diff --git a/lib/gitlab/utils/json_size_estimator.rb b/lib/gitlab/utils/json_size_estimator.rb new file mode 100644 index 00000000000..9f8ea3e61f9 --- /dev/null +++ b/lib/gitlab/utils/json_size_estimator.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +module Gitlab + module Utils + # This class estimates the JSON blob byte size of a ruby object using as + # little allocations as possible. + # The estimation should be quite accurate when using simple objects. + # + # Example: + # + # Gitlab::Utils::JsonSizeEstimator.estimate(["a", { b: 12, c: nil }]) + class JsonSizeEstimator + ARRAY_BRACKETS_SIZE = 2 # [] + OBJECT_BRACKETS_SIZE = 2 # {} + DOUBLEQUOTE_SIZE = 2 # "" + COLON_SIZE = 1 # : character size from {"a": 1} + MINUS_SIGN_SIZE = 1 # - character size from -1 + NULL_SIZE = 4 # null + + class << self + # Returns: integer (number of bytes) + def estimate(object) + case object + when Hash + estimate_hash(object) + when Array + estimate_array(object) + when String + estimate_string(object) + when Integer + estimate_integer(object) + when Float + estimate_float(object) + when DateTime, Time + estimate_time(object) + when NilClass + NULL_SIZE + else + # might be incorrect, but #to_s is safe, #to_json might be disabled for some objects: User + estimate_string(object.to_s) + end + end + + private + + def estimate_hash(hash) + size = 0 + item_count = 0 + + hash.each do |key, value| + item_count += 1 + + size += estimate(key.to_s) + COLON_SIZE + estimate(value) + end + + size + OBJECT_BRACKETS_SIZE + comma_count(item_count) + end + + def estimate_array(array) + size = 0 + item_count = 0 + + array.each do |item| + item_count += 1 + + size += estimate(item) + end + + size + ARRAY_BRACKETS_SIZE + comma_count(item_count) + end + + def estimate_string(string) + string.bytesize + DOUBLEQUOTE_SIZE + end + + def estimate_float(float) + float.to_s.bytesize + end + + def estimate_integer(integer) + if integer > 0 + integer_string_size(integer) + elsif integer < 0 + integer_string_size(integer.abs) + MINUS_SIGN_SIZE + else # 0 + 1 + end + end + + def estimate_time(time) + time.to_json.size + end + + def integer_string_size(integer) + Math.log10(integer).floor + 1 + end + + def comma_count(item_count) + item_count == 0 ? 0 : item_count - 1 + end + end + end + end +end |