有选择地展平嵌套的JSON结构

时间:2016-09-24 05:31:26

标签: javascript python json elasticsearch clojure

所以这是一个问题,我不知道从哪里开始,所以即使只是一个正确方向的指针也会很棒。

所以我的数据看起来像这样:

data = {
   "agg": {
      "agg1": [
         {
            "keyWeWant": "*-20.0",
            "asdf": 0,
            "asdf": 20,
            "asdf": 14,
            "some_nested_agg": [
               {
                  "keyWeWant2": 20,
                  "to": 25,
                  "doc_count": 4,
                  "some_nested_agg2": {
                     "count": 7,
                     "min": 2,
                     "max": 5,
                     "keyWeWant3": 2.857142857142857,
                     "sum": 20
                  }
               },
               {
                  "keyWeWant2": 25,
                  "to": 30,
                  "doc_count": 10,
                  "some_nested_agg2": {
                     "count": 16,
                     "min": 2,
                     "max": 10,
                     "keyWeWant3": 6.375,
                     "sum": 102
                  }
               }
            ]
         },
         {
         ...
         },
         {
         ...
         },
         ...
      ]
   }
}

现在从示例中,在'agg'中有N'agg1'结果,在每个'agg1'结果中有一个'keyWeWant'。每个'agg1'结果还有一个'some_nested_agg'结果列表,每个结果都包含一个'keyWeWant2'。每个'keyWeWant2'值与层次结构中某处的单个'keyWeWant'值相关联。类似地,每个'keyWeWant2'也包含'some_nested_agg2'的一组结果(这不是列表,而是这次的映射)。每组结果都包含一个'keyWeWant3'。

现在我想要展平这个结构,同时仍然保留'keyWeWant','keyWeWant2'和'keyWeWant3'之间的关联(我基本上是去标准化)以获得类似的东西:

我想要的功能如下:

[
   {
      "keyWeWant" : "*-20",
      "keyWeWant2" : 20,
      "keyWeWant3" : 2.857142857142857
   },
   {
      "keyWeWant" : "*-20",
      "keyWeWant2" : 25,
      "keyWeWant3" : 6.375
   },
   {
   ...
   },
   {
   ...
   }
]

这是一个只有深度为3但是可以​​有任意深度的示例,其中一些嵌套值是列表,一些是数组/列表。

我想要做的是编写一个函数来接收我想要的密钥以及在哪里找到它们,然后去获取密钥并进行反规范化。

看起来像:

function_name(data_map, {
   "keyWeWant" : ['agg', 'agg1'],
   "keyWeWant2" : ['agg', 'agg1', 'some_nested_agg'],
   "keyWeWant" : ['agg', 'agg1', 'some_nested_agg', 'some_nested_agg2']
})

有什么想法吗?我熟悉Java,Clojure,Java脚本和Python,我只是想找到一种解决这个问题的方法,这个方法相对简单。

2 个答案:

答案 0 :(得分:1)

可能有更好的方法来解决这个特定问题(使用一些ElasticSearch库或其他东西),但这里是Clojure中使用您请求的输入和输出数据格式的解决方案。

我将此测试数据放在名为data.json的文件中:

{
    "agg": {
        "agg1": [
            {
                "keyWeWant": "*-20.0",
                "asdf": 0,
                "asdf": 20,
                "asdf": 14,
                "some_nested_agg": [
                    {
                        "keyWeWant2": 20,
                        "to": 25,
                        "doc_count": 4,
                        "some_nested_agg2": {
                            "count": 7,
                            "min": 2,
                            "max": 5,
                            "keyWeWant3": 2.857142857142857,
                            "sum": 20
                        }
                    },
                    {
                        "keyWeWant2": 25,
                        "to": 30,
                        "doc_count": 10,
                        "some_nested_agg2": {
                            "count": 16,
                            "min": 2,
                            "max": 10,
                            "keyWeWant3": 6.375,
                            "sum": 102
                        }
                    }]
            }]}
}

然后Cheshire JSON library将数据解析为Clojure数据结构:

(use '[cheshire.core :as cheshire])

(def my-data (-> "data.json" slurp cheshire/parse-string))

接下来,get的路径定义如下:

(def my-data-map
  {"keyWeWant"  ["agg", "agg1"],
   "keyWeWant2" ["agg", "agg1", "some_nested_agg"],
   "keyWeWant3" ["agg", "agg1", "some_nested_agg", "some_nested_agg2"]})

您的data_map以上没有“:”,单引号更改为双引号,最后一个“keyWeWant”更改为“keyWeWant3”。

下面的

find-nested具有Clojure的get-in的语义,只有它适用于带向量的地图,并返回所有值而不是一个。 当find-nested被赋予搜索向量时,它会在嵌套映射中找到所有值,其中某些值可以包含带有映射列表的向量。检查向量中的每个地图。

(defn find-nested
  "Finds all values in a coll consisting of maps and vectors.

  All values are returned in a tree structure:
  i.e, in your problem it returns (20 25) if you call it with
  (find-nested ['agg', 'agg1', 'some_nested_agg', 'keyWeWant2'] 
  my-data).

  Returns nil if not found."
  [ks c]
  (let [k (first ks)]
    (cond (nil? k)    c
          (map? c)    (find-nested (rest ks) (get c k))
          (vector? c) (if-let [e (-> c first (get k))]
                        (if (string? e) e ; do not map over chars in str
                            (map (partial find-nested (rest ks)) e))
                        (find-nested ks (into [] (rest c)))) ; create vec again
          :else       nil)))

find-nested找到搜索路径的值:

(find-nested ["agg", "agg1", "some_nested_agg", "keyWeWant2"] my-data) 
; => (20 25)

如果所有通往“keyWeWant”的路径都映射到my-data,那么这些路径就是tree的切片:

  

(* - 20.0
   (20 25)
   (2.857142857142857 6.375))

您要求的结构(路径到达的所有最终结果)都可以从tree function-name中获取,如下所示:

(defn function-name
  "Transforms data d by finding (nested keys) via data-map m in d and 
  flattening the structure."
  [d m]
  (let [tree               (map #(find-nested (conj (second %) (first %)) d) m)
        leaves             (last tree)
        leaf-indices       (range (count leaves))
        results            (for [index leaf-indices]
                             (map (fn [slice]
                                    (if (string? slice)
                                      slice
                                      (loop [node (nth slice index)]
                                        (if node
                                          node
                                          (recur (nth slice (dec index)))))))
                                  tree))
        results-with-paths (mapv #(zipmap (keys m) %) results)
        json               (cheshire/encode results-with-paths)]
    json))
如果results大于特定切片,

leaf-index会使用loop退一步。我认为它也适用于更深层次的嵌套结构 - 如果下一个切片的大小总是前一个切片的两倍或者它应该达到的相同大小 - 但是我还没有测试过它。

致电(function-name my-data my-data-map)会以您要求的格式生成JSON字符串:

  

[{
  “keyWeWant”:“ -20.0”,
  “keyWeWant2”:20,
  “keyWeWant3”:2.857142857142857}
  {
  “keyWeWant”:“
-20.0”,
  “keyWeWant2”25,
  “keyWeWant3”6.375}]

/编辑 我看到你正在寻找一个相对简单的解决方案,但事实并非如此。 :-)也许有一个没有它在库中。我很高兴知道如何简化它。

答案 1 :(得分:1)

以下是您可以使用的JavaScript(ES6)功能:

function flatten(data, keys) {
    var key = keys[0];
    if (key in data)
        keys = keys.slice(1);
    var res = keys.length && Object.keys(data)
        .map( key => data[key] )
        .filter( val => Object(val) === val )
        .reduce( (res, val) => res.concat(flatten(val, keys)), []);
    return !(key in data) ? res
        : (res || [{}]).map ( obj => Object.assign(obj, { [key]: data[key] }) );
}

// Sample data
var data = {
   "agg": {
      "agg1": [
         {
            "keyWeWant": "*-20.0",
            "asdf": 0,
            "asdf": 20,
            "asdf": 14,
            "some_nested_agg": [
               {
                  "keyWeWant2": 20,
                  "to": 25,
                  "doc_count": 4,
                  "some_nested_agg2": {
                     "count": 7,
                     "min": 2,
                     "max": 5,
                     "keyWeWant3": 2.857142857142857,
                     "sum": 20
                  }
               },
               {
                  "keyWeWant2": 25,
                  "to": 30,
                  "doc_count": 10,
                  "some_nested_agg2": {
                     "count": 16,
                     "min": 2,
                     "max": 10,
                     "keyWeWant3": 6.375,
                     "sum": 102
                  }
               }
            ]
         },
      ]
   }
};

// Flatten it by array of keys
var res = flatten(data, ['keyWeWant', 'keyWeWant2', 'keyWeWant3']);

// Output result
console.log(res);

使用路径替代

如评论中所述,上述代码不使用路径信息;它只是在所有数组中查找。如果要查找的键也出现在应该被忽略的路径中,这可能是一个问题。

以下替代方法将使用路径信息,路径信息应作为子数组数组传递,其中每个子数组首先列出路径键,最后一个元素是要保留的值键:

function flatten(data, [path, ...paths]) {
    return path && (
        Array.isArray(data)
            ? data.reduce( (res, item) => res.concat(flatten(item, arguments[1])), [] )
            : path[0] in data && (
                path.length > 1 
                    ? flatten(data[path[0]], [path.slice(1), ...paths])
                    : (flatten(data, paths) || [{}]).map ( 
                        item => Object.assign(item, { [path[0]]: data[path[0]] }) 
                    )
            )
    );
}

// Sample data
var data = {
   "agg": {
      "agg1": [
         {
            "keyWeWant": "*-20.0",
            "asdf": 0,
            "asdf": 20,
            "asdf": 14,
            "some_nested_agg": [
               {
                  "keyWeWant2": 20,
                  "to": 25,
                  "doc_count": 4,
                  "some_nested_agg2": {
                     "count": 7,
                     "min": 2,
                     "max": 5,
                     "keyWeWant3": 2.857142857142857,
                     "sum": 20
                  }
               },
               {
                  "keyWeWant2": 25,
                  "to": 30,
                  "doc_count": 10,
                  "some_nested_agg2": {
                     "count": 16,
                     "min": 2,
                     "max": 10,
                     "keyWeWant3": 6.375,
                     "sum": 102
                  }
               }
            ]
         },
      ]
   }
};

// Flatten it by array of keys
var res = flatten(data, [
    ['agg', 'agg1', 'keyWeWant'], 
    ['some_nested_agg', 'keyWeWant2'], 
    ['some_nested_agg2', 'keyWeWant3']]);

// Output result
console.log(res);