在 JSON 中查找键的行号

Find line number of key in JSON

给定一个 JSON 文件和一个键的路径,我需要能够获取存储该值的行号。需要多行的值(例如,数组)目前超出范围,直到我弄清楚如何处理最简单的情况。例如,在下面的 JSON:

{                        # Line 1
    "foo": {             # Line 2
        "bar": [
            "value1",
            "value2"
        ],
        "bar2": 2        # Line 7
    },
    "bar": {
        "bar": [
            "value1",
            "value2"
        ],
        "bar2": 5
    }
}

我在寻找关键路径foo.bar2时应该得到7

我有一个可行的解决方案,假设原始文件的格式为 JSON.pretty_generate:

parsed_json = File.read(file)
random_string = SecureRandom.uuid

parsed_json.bury(*path.split('.'), random_string)

JSON.pretty_generate(parsed_json)
    .split("\n")
    .take_while { |s| s.exclude? random_string }
    .count + 1

我在这里做的是解析 JSON 文件,将现有值替换为随机字符串(在本例中为 UUID),将散列格式化为漂亮的 JSON,以及找到随机字符串所在的行。这里使用的 Hash.bury 方法按照 https://bugs.ruby-lang.org/issues/11747.

中的定义工作

这个解决方案工作正常(虽然还没有经过严格测试),但是当原始文件没有被格式化为漂亮的打印时,我很难让它工作 JSON。例如,以下文件将等同于上面的文件:

{                                     # Line 1
    "foo": {                          # Line 2
        "bar": ["value1","value2"],  
        "bar2": 2                     # Line 4
    },
    "bar": {
        "bar": [
            "value1",
            "value2"
        ],
        "bar2": 5
    }
}

但是上面的算法仍然会return7作为foo.bar2所在的行,虽然现在它在4.

有什么方法可以可靠地获取 JSON 文件中放置密钥的行号?

我有一个想法:使用相同的 uuid 技巧(顺便说一句,这很棒),比较你的 json 和文件内容,但忽略每个空白 space.

一旦你找到不同的字符,它应该是你想要的行。

我写了一个似乎可以工作的程序,没有经过严格测试;)(我复制了 bury 定义):

require 'json'
require 'readline'
require 'securerandom'

class Hash
  def bury *args
    if args.count < 2
      raise ArgumentError.new("2 or more arguments required")
    elsif args.count == 2
      self[args[0]] = args[1]
    else
      arg = args.shift
      self[arg] = {} unless self[arg]
      self[arg].bury(*args) unless args.empty?
    end
    self
  end
end

file_path = 'your/file/path.json'
path = 'foo.bar2'

file_content = File.read(file_path)
parsed_json = JSON.parse(file_content)
uuid = SecureRandom.uuid

parsed_json.bury(*path.split('.'), uuid)

compacted_json = JSON.pretty_generate(parsed_json).gsub(/\s/, '')
@file_lines = File.readlines(file_path)

index = 0
loop do
  compact_line = @file_lines[index].gsub(/\s/, '')
  break if !compacted_json.start_with?(compact_line)
  index += 1
  compacted_json = compacted_json[compact_line.length..-1]
end

puts "line is #{index+1}"

这是我在不构建自己的 JSON 解析器的情况下找到的最简单的方法:用唯一的 UUID(别名)替换 每个 键条目,然后构建别名的所有组合并找到来自 #dig 的 returns 数据调用

keys = path.split('.')
file_content = File.read(file_path).gsub('null', '1111')
aliases = {}

keys.each do |key|
  pattern = "\"#{key}\":"

  file_content.scan(pattern).each do
    alias_key = SecureRandom.uuid
    file_content.sub!(pattern, "\"#{alias_key}\":")

    aliases[key] ||= []
    aliases[key] << alias_key
  end
end

winner = aliases.values.flatten.combination(keys.size).find do |alias_keys|
  # nulls were gsubbed above to make this check work in edge case when path value is null
  JSON.parse(file_content).dig(*alias_keys).present?
end

file_content.split("\n").take_while { |line| line.exclude?(winner.last) }.count + 1

UPD:如果 foo.bar2 键的 JSON 值为 false,则上面的代码段将不起作用。您也应该 gsub 它或使此代码段更智能