@@ -17,15 +17,15 @@ def self.import_from_file(file_content, user, on_progress: nil, progress_interva
1717 heartbeat_batch . clear
1818 end
1919
20- handler = HeartbeatSaxHandler . new do |hb |
20+ handler = HeartbeatStreamHandler . new do |hb |
2121 total_count += 1
2222 on_progress &.call ( total_count ) if progress_interval . positive? && ( total_count % progress_interval ) . zero?
2323
2424 heartbeat_batch << hb
2525 flush . call if heartbeat_batch . size >= BATCH_SIZE
2626 end
2727
28- Oj . saj_parse ( handler , file_content )
28+ Oj . sc_parse ( handler , file_content )
2929 on_progress &.call ( total_count )
3030
3131 raise StandardError , "Expected a heartbeat export JSON file." if total_count . zero?
@@ -40,54 +40,56 @@ def self.import_from_file(file_content, user, on_progress: nil, progress_interva
4040 skipped_count : total_count - imported_count , errors : errors + [ e . message ] }
4141 end
4242
43- class HeartbeatSaxHandler < Oj ::Saj
43+ # Retain only the current heartbeat, not the surrounding dump or day arrays.
44+ # Unlike SAJ, ScHandler preserves embedded NULs for ingestion to sanitize.
45+ class HeartbeatStreamHandler < Oj ::ScHandler
4446 def initialize ( &block )
4547 @block = block
4648 @depth = 0
47- @current_heartbeat = nil
4849 @heartbeat_array_depths = [ ]
49- @field_array_stack = [ ]
5050 end
5151
52- def hash_start ( key )
53- @current_heartbeat = { } if inside_heartbeat_array? && @depth == @heartbeat_array_depths . last + 1
52+ def hash_start
53+ @key = nil
5454 @depth += 1
55+ { } if @heartbeat_array_depths . any?
5556 end
5657
57- def hash_end ( key )
58+ def hash_end
5859 @depth -= 1
59- if inside_heartbeat_array? && @depth == @heartbeat_array_depths . last + 1 && @current_heartbeat
60- @block . call ( @current_heartbeat )
61- @current_heartbeat = nil
62- end
6360 end
6461
65- def array_start ( key )
66- @heartbeat_array_depths << @depth if key == "heartbeats"
67- if @current_heartbeat && key . present?
68- @current_heartbeat [ key ] = [ ]
69- @field_array_stack << key
62+ def hash_key ( key )
63+ @key = key
64+ end
65+
66+ def hash_set ( hash , key , value )
67+ hash [ key ] = value if hash
68+ end
69+
70+ def array_start
71+ container = if @key == "heartbeats" && @heartbeat_array_depths . empty?
72+ @heartbeat_array_depths << @depth
73+ :heartbeats
74+ elsif @heartbeat_array_depths . any?
75+ [ ]
7076 end
77+ @key = nil
7178 @depth += 1
79+ container
7280 end
7381
74- def array_end ( key )
82+ def array_end
7583 @depth -= 1
76- @heartbeat_array_depths . pop if key == "heartbeats" && @heartbeat_array_depths . last == @depth
77- @field_array_stack . pop if @field_array_stack . last == key
84+ @heartbeat_array_depths . pop if @heartbeat_array_depths . last == @depth
7885 end
7986
80- def add_value ( value , key )
81- return unless @current_heartbeat
82- if key
83- @current_heartbeat [ key ] = value
84- elsif @field_array_stack . any?
85- @current_heartbeat [ @field_array_stack . last ] << value
87+ def array_append ( array , value )
88+ if array == :heartbeats
89+ @block . call ( value )
90+ elsif array
91+ array << value
8692 end
8793 end
88-
89- private
90-
91- def inside_heartbeat_array? = @heartbeat_array_depths . any?
9294 end
9395end
0 commit comments