PandaCool PandaCool - 4 months ago 15
JSON Question

Flat hash to json hash tree with a hierarchy

I have a hash with my hierarchy like

['country', 'city', 'street', ...]

who's the head to last son of my tree.

My data look like

{"country": "france", "city": "paris", "street": "champs elysee"},
{"country": "france", "city": "Lyon", "street": "lyon street"},
{"country": "italy", "city": "rome", "street": "rome street1"},
...


I would like to transform this into a tree like

[
{
"france" => [
{
"paris" => {"champs elysee" => [...]},
"lyon" => {"lyon street1" => [...]}
}
]
},
{
"italy" => [
{
"rome" => {"rome street 1" => [...]},
...
}
]
},
]


And be able to change my hierarchy

I tried many things but i didn't find any solution.. I think to didn't have the right approach.

EDIT :

This is my code (Not working at all) but it's the idea.

require 'json'

@hierarchie = ["country", "city", "street"]

f = File.read("./data.json")
@ret = JSON.parse(f)

@tree = []
def render_as_tree
@ret.each do |datum|
ptr = @tree
@hierarchie.each do |h|
if ptr.empty?
ptr << {datum[h] => []}
end
obj = ptr.detect {|d| datum[h]}
ptr = obj[ datum[h] ]
end
end
return @tree
end

puts render_as_tree()


This code is a small exemple of my code. My code is not with country/city/street but it's better for understanding and explanation.

The
...
means the number of child could change dynamicly. For this exemple i could have
country/city/district/street/house/...
or just
country/city
so my last data need to be the flat data.

Answer

Thanks to @ProGM i found the solution !

How to deep merge two multi-root tree structure made of array and hashes

class Tree
  attr_reader :data

  def initialize(data)
    @data = data
  end

  def +(other)
    Tree.new(deep_merge(@data, other.is_a?(Tree) ? other.data : other))
  end

  delegate :empty?, to: :data

  def replace(other)
    @data.replace(other.data)
  end

  def root
    level(0)
  end

  def levels
    output = []
    i = -1
    while i += 1
      current_level = level(i)
      break if current_level.all?(&:nil?)
      output << current_level
    end
    output
  end

  def level(level_number)
    all_elements_at_level(@data, level_number)
  end

  private

  def deep_merge(a, b)
    case a
    when Hash
      return merge_hashes(a, b) if b.is_a?(Hash)
      return merge_array_hash(b, a) if b.is_a?(Array)
      [b, a]
    when Array
      return merge_arrays(a, b) if b.is_a?(Array)
      return merge_array_hash(a, b) if b.is_a?(Hash)
      [b] + a
    else
      return [a, b] if b.is_a?(Hash)
      return [a] + b if b.is_a?(Array)
      a == b ? a : [a, b]
    end
  end

  def merge_array_hash(a, b)
    if a.last.is_a? Hash
      a[0...-1] + [merge_hashes(a.last, b)]
    else
      a + [b]
    end
  end

  def merge_hashes(a, b)
    a.deep_merge(b) do |_, this_val, other_val|
      deep_merge(this_val, other_val)
    end
  end

  def merge_arrays(a, b)
    keys = merge_array_keys(a, b)
    hashes = merge_hashes(a.last.is_a?(Hash) ? a.last : {}, b.last.is_a?(Hash) ? b.last : {})
    if hashes.empty?
      keys
    else
      (keys - hashes.keys) + [hashes]
    end
  end

  def merge_array_keys(a, b)
    (a.reject { |e| e.is_a?(Hash) } + b.reject { |e| e.is_a?(Hash) }).uniq
  end

  def all_elements_at_level(data, level_number)
    return ground_level(data) if level_number == 0
    case data
    when Hash
      data.map { |_, v| all_elements_at_level(v, level_number - 1) }
    when Array
      data.map { |e| all_elements_at_level(e, level_number) }.flatten
    end
  end

  def ground_level(data)
    case data
    when Hash
      data.keys
    when Array
      data.map { |e| all_elements_at_level(e, 0) }.flatten
    else
      data
    end
  end
end
Comments