Module: FormatParser::AttributesJSON

Included in:
Archive, Audio, Document, Image, MP3Parser::TagWrapper, Text, Video, ZIPParser::FileReader::ZipEntry
Defined in:
lib/attributes_json.rb

Overview

Implements as_json as returning a Hash containing the return values of all the reader methods of an object that have associated pair writer methods.

class Foo
  include AttributesJSON
  attr_accessor :number_of_bars
end
the_foo = Foo.new
the_foo.number_of_bars = 42
the_foo.as_json #=> {:number_of_bars => 42}

Constant Summary collapse

MAXIMUM_JSON_NESTING_WHEN_SANITIZING =
256

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

._sanitize_json_value(value, nesting = 0) ⇒ Object

Used for sanitizing values that are sourced to ‘JSON::Generator::State#generate` The reason we need to do this is as follows: `JSON.generate / JSON.dump / JSON.pretty_generate` use a totally different code path than `“foo”.to_json(generator_state)`. We cannot predict which one of these two ways our users will be using, and at the same time we need to prevent invalid Strings (ones which cannot be encoded into UTF-8) as well as Float::INFINITY values from being passed to the JSON encoder. Since we cannot override the JSON generator with these additions, instead we will deep-convert the entire object being output to make sure it is up to snuff.

Raises:

  • (ArgumentError)


53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/attributes_json.rb', line 53

def _sanitize_json_value(value, nesting = 0)
  raise ArgumentError, 'Nested JSON-ish structure too deep' if nesting > MAXIMUM_JSON_NESTING_WHEN_SANITIZING
  case value
  when Float
    # Float::NAN cannot be case-matched as it is does not equal itself. Float::INIFINITY can,
    # but it is easier to fold these two into a single case
    if value.nan? || value.infinite?
      nil
    else
      value
    end
  when String
    FormatParser.string_to_lossy_utf8(value)
  when Hash
    Hash[value.map { |k, v| [_sanitize_json_value(k, nesting + 1), _sanitize_json_value(v, nesting + 1)] }]
  when Array
    value.map { |v| _sanitize_json_value(v, nesting + 1) }
  when Struct
    _sanitize_json_value(value.to_h, nesting + 1)
  else
    value
  end
end

Instance Method Details

#as_json(root: false, stringify_keys: false) ⇒ Object

Implements a sane default ‘as_json` for an object that accessors defined

Parameters:

  • root (Bool) (defaults to: false)

    if true, it surrounds the result in a hash with a key ‘format_parser_file_info`

  • stringify_keys (Bool) (defaults to: false)

    if true, it transforms all the hash keys to a string. The default value is false for backward compatibility



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/attributes_json.rb', line 23

def as_json(root: false, stringify_keys: false, **)
  h = {}
  h['nature'] = nature if respond_to?(:nature) # Needed for file info structs
  methods.grep(/\w\=$/).each_with_object(h) do |attr_writer_method_name, h|
    reader_method_name = attr_writer_method_name.to_s.gsub(/\=$/, '')
    attribute_value = public_send(reader_method_name)
    # When calling as_json on our members there is no need to pass
    # the root: option given to us by the caller
    unwrapped_attribute_value = attribute_value.respond_to?(:as_json) ? attribute_value.as_json : attribute_value
    sanitized_value = _sanitize_json_value(unwrapped_attribute_value)
    h[reader_method_name] = sanitized_value
  end

  h = FormatParser::HashUtils.deep_transform_keys(h, &:to_s) if stringify_keys

  if root
    {'format_parser_file_info' => h}
  else
    h
  end
end

#to_json(*maybe_generator_state) ⇒ Object

Implements to_json with sane defaults, with or without arguments



79
80
81
# File 'lib/attributes_json.rb', line 79

def to_json(*maybe_generator_state)
  as_json(root: false).to_json(*maybe_generator_state)
end