module JsonDiffing

Private Instance Methods

count_path?(path, inclusion) click to toggle source
# File lib/diff_json/diff/json_diffing.rb, line 185
def count_path?(path, inclusion)
  inclusion_base     = inclusion.gsub(/\/?\**$/, '')
  inclusion_wildcard = /\**$/.match(inclusion)[0]

  if path.include?(inclusion_base)
    trailing_elements = path.gsub(/^#{inclusion_base}\/?/, '').split('/').length

    return true if (
      (trailing_elements == 0 and inclusion_wildcard == '') or
      (trailing_elements == 1 and inclusion_wildcard == '*') or
      (trailing_elements > 0 and inclusion_wildcard == '**')
    )
  end

  return false
end
diff_array(old_array, new_array, base_path) click to toggle source
# File lib/diff_json/diff/json_diffing.rb, line 27
def diff_array(old_array, new_array, base_path)
  return {} if old_array == new_array

  diff_operations = {}
  add_drop_operations = {}
  last_shared_index = (old_array.length - 1)

  if @opts[:replace_primitives_arrays]
    if @old_map[base_path][:array_type] == :primitives and @new_map[base_path][:array_type] == :primitives
      diff_operations[base_path] = [{op: :replace, path: base_path, from: old_array, value: new_array}]
      return diff_operations
    end
  end

  if @opts[:track_array_moves]
    old_array_map   = old_array.each_with_index.map{|v,i| [i, v]}
    new_array_map   = new_array.each_with_index.map{|v,i| [i, v]}
    shared_elements = (old_array_map & new_array_map)
    old_move_check  = (old_array_map - shared_elements)
    new_move_check  = (new_array_map - shared_elements)
    possible_moves  = []
    max_moves       = (old_move_check.length < new_move_check.length ? old_move_check.length : new_move_check.length)

    if max_moves > 0
      old_move_check.each do |omc|
        destinations = new_move_check.map{|v| omc[1] == v[1] ? [(omc[0] - v[0]).abs, omc[0], v[0]] : nil}.compact.sort_by{|x| x[0]}
        if !destinations.empty? and possible_moves.length < max_moves
          possible_moves << {op: :move, from: "#{base_path}/#{destinations.first[1]}", path: "#{base_path}/#{destinations.first[2]}"}
        end
      end
    end
  end

  if new_array.length > old_array.length
    new_array[(old_array.length)..(new_array.length - 1)].each_with_index do |value, i|
      element_path = "#{base_path}/#{(old_array.length + i)}"
      add_drop_operations[element_path] = [{op: :add, path: element_path, value: value}]

      if @opts[:track_array_moves]
        element_move_search = possible_moves.select{|x| x[:path] == element_path}
        add_drop_operations[element_path] += element_move_search
      end
    end
  elsif old_array.length > new_array.length
    last_shared_index = new_array.length - 1

    old_array[(new_array.length)..(old_array.length - 1)].each_with_index do |value, i|
      element_index = (new_array.length + i)
      element_path = "#{base_path}/#{element_index}"
      add_drop_operations[element_path] = [{op: :remove, path: element_path, value: old_array[element_index]}]

      if @opts[:track_array_moves]
        element_move_search = possible_moves.select{|x| x[:from] == element_path}
        add_drop_operations[element_path] += element_move_search
      end
    end
  end

  (0..last_shared_index).each do |i|
    index_path = "#{base_path}/#{i}"

    if @opts[:track_array_moves]
      element_move_search = possible_moves.select{|x| x[:from] == index_path or x[:path] == index_path}
      element_move_search << {op: :replace, path: index_path, value: new_array[i]} if element_move_search.length == 1
      diff_operations.merge!(element_move_search.empty? ? diff_check(old_array[i], new_array[i], index_path) : {index_path => element_move_search})
    else
      unless @opts[:ignore_paths].include?(index_path)
        diff_operations.merge!(diff_check(old_array[i], new_array[i], index_path))
      else
        diff_operations[index_path] = [{op: :ignore}]
      end
    end
  end

  diff_operations.merge!(add_drop_operations)

  return diff_operations
end
diff_check(old_element, new_element, base_path = '') click to toggle source
# File lib/diff_json/diff/json_diffing.rb, line 4
def diff_check(old_element, new_element, base_path = '')
  diff_operations = {}

  if (old_element.is_a?(Array) and new_element.is_a?(Array)) or (old_element.is_a?(Hash) and new_element.is_a?(Hash))
    element_operations = case old_element.class.name
    when 'Array'
      diff_array(old_element, new_element, base_path)
    when 'Hash'
      diff_hash(old_element, new_element, base_path)
    end

    if @opts[:track_structure_updates]
      element_operations[base_path] = [{op: :update}] if element_operations.select{|k,v| count_path?(k, "#{base_path}/*")}.length > 0
    end

    diff_operations.merge!(element_operations)
  else
    diff_operations[base_path] = [{op: :replace, path: base_path, from: old_element, value: new_element}] unless old_element == new_element
  end

  return diff_operations
end
diff_hash(old_hash, new_hash, base_path) click to toggle source
# File lib/diff_json/diff/json_diffing.rb, line 106
def diff_hash(old_hash, new_hash, base_path)
  return {} if old_hash == new_hash

  diff_operations = {}
  old_keys, new_keys = old_hash.keys, new_hash.keys
  common_keys, added_keys, dropped_keys = (old_keys & new_keys), (new_keys - old_keys), (old_keys - new_keys)

  common_keys.each do |ck|
    element_path = "#{base_path}/#{ck}"

    unless @opts[:ignore_paths].include?(element_path)
      diff_operations.merge!(diff_check(old_hash[ck], new_hash[ck], element_path))
    else
      diff_operations[element_path] = [{op: :ignore}]
    end
  end

  added_keys.each do |ak|
    element_path = "#{base_path}/#{ak}"
    diff_operations[element_path] = [{op: :add, path: element_path, value: new_hash[ak]}]
  end

  dropped_keys.each do |dk|
    element_path = "#{base_path}/#{dk}"
    diff_operations[element_path] = [{op: :remove, path: element_path}]
  end

  return diff_operations
end
find_counts(diff_structure) click to toggle source
# File lib/diff_json/diff/json_diffing.rb, line 152
def find_counts(diff_structure)
  counts = {
    ignore: 0,
    add: 0,
    replace: 0,
    remove: 0
  }
  counts[:move]   = 0 if @opts[:track_array_moves]
  counts[:update] = 0 if @opts[:track_structure_updates]

  diff_structure.each do |path, operations|
    inclusion = path_inclusion(path)

    operations.each do |op|
      counts[op[:op]] += 1 if (inclusion.include?(op[:op]) and ([:ignore, :add, :replace, :remove, :update].include?(op[:op]) or (op[:op] == :move and path == op[:from])))
    end
  end

  return counts
end
generate_sub_diffs() click to toggle source
# File lib/diff_json/diff/json_diffing.rb, line 136
def generate_sub_diffs
  sub_diffs = {}

  @opts[:sub_diffs].each do |k,v|
    sub_diff_paths = @all_paths.select{|x| count_path?(x, k)}
    old_elements = @old_map.select{|k,v| sub_diff_paths.include?(k)}.values.map{|x| {x[:value][v[:key]] => x[:value]}}.reduce(:merge)
    new_elements = @new_map.select{|k,v| sub_diff_paths.include?(k)}.values.map{|x| {x[:value][v[:key]] => x[:value]}}.reduce(:merge)

    (old_elements.keys + new_elements.keys).uniq.each do |sub_diff_id|
      sub_diffs["#{k}::#{sub_diff_id}"] = DiffJson::Diff.new((old_elements[sub_diff_id] || {}), (new_elements[sub_diff_id] || {}), **v[:opts]) unless old_elements[sub_diff_id] == new_elements[sub_diff_id]
    end
  end

  return sub_diffs
end
path_inclusion(path) click to toggle source
# File lib/diff_json/diff/json_diffing.rb, line 173
def path_inclusion(path)
  if @opts[:count_operations].include?('**')
    return @opts[:count_operations]['**']
  else
    @opts[:count_operations].each do |path_set, operations|
      return operations if count_path?(path, path_set)
    end

    return []
  end
end