【问题标题】:JavaScript: Transform from one nested JSON format to another nested JSON formatJavaScript:从一种嵌套 JSON 格式转换为另一种嵌套 JSON 格式
【发布时间】:2020-12-21 01:43:53
【问题描述】:

我正在尝试将 AVRO 模式转换为 ElasticSearch 索引模板。两者都是 JSON 结构,在转换时需要检查一些内容。我尝试使用递归来取出所有嵌套元素,然后将它们与它们的父元素配对,但是在使用递归进行深度解析时写入字典迫使我提出这个问题。

所以基本上我有这个 AVRO 架构文件:

{
    "name": "animal",
    "type": [
        "null",
        {
            "type": "record",
            "name": "zooAnimals",
            "fields": [{
                    "name": "color",
                    "type": ["null", "string"],
                    "default": null
                },
                {
                    "name": "skinType",
                    "type": ["null", "string"],
                    "default": null
                },
                {
                    "name": "species",
                    "type": {
                        "type": "record",
                        "name": "AnimalSpecies",
                        "fields": [{
                                "name": "terrestrial",
                                "type": "string"
                            },
                            {
                                "name": "aquatic",
                                "type": "string"
                            }
                        ]
                    }
                },
                {
                    "name": "behavior",
                    "type": [
                        "null",
                        {
                            "type": "record",
                            "name": "AnimalBehaviors",
                            "fields": [{
                                    "name": "sound",
                                    "type": ["null", "string"],
                                    "default": null
                                },
                                {
                                    "name": "hunt",
                                    "type": ["null", "string"],
                                    "default": null
                                }
                            ]
                        }
                    ],
                    "default": null
                }
            ]
        }
    ]
}

我希望它转换成这个(Elasticsearch 索引模板格式):

{
    "properties": {
        "color" :{
            "type" : "keyword"
        },
        "skinType" :{
            "type" : "keyword"
        },
        "species" :{
            "properties" : {
                "terrestrial" : {
                    "type" : "keyword"
                },
                "aquatic" : {
                    "type" : "keyword"
                },
            }
           
        },
        "behavior" : {
            "properties" : {
                "sound" : {
                    "type" : "keyword"
                },
                "hunt" : {
                    "type" : "keyword"
                }
            }
        }
    }
}

重要提示:AVRO 模式上的嵌套可以进一步嵌套,这就是我考虑使用递归解决的原因。此外,type 字段的类型可以是ArrayMap,如behaviorspecies 所示,其中行为有一个数组,物种有一个映射。

如果你必须看到我做了我的试验和错误,这是我的代码,没有让我到任何地方:

const checkDataTypeFromObject = function (obj) {

  if (Object.prototype.toString.call(obj) === "[object Array]") {
    obj.map(function (item) {
      if (Object.prototype.toString.call(item) === "[object Object]") {
        // so this is an object that could contain further nested fields
        dataType = item;
        mappings.properties[item.name] = { "type" : item.type}
         if (item.hasOwnProperty("fields")) {
          checkDataTypeFromObject(item.fields);
        } else if (item.hasOwnProperty("type")) {
          checkDataTypeFromObject(item.type);
        } 
      } else if (item === null) {
        // discard the nulls, nothing to do here
      } else {
        // if not dict or null, this is the dataType we are looking for
        dataType = item;
      }

      return item.name
    });

【问题讨论】:

  • 输入 JSON 与有效的 AVRO 模式不对应。看看 {"name":"animal","type":["null",{.... 字段 "type" 中没有对象。类型是字符串或字符串数​​组,字符串值为以下之一:null、int、long、float、double、bytes、string、record、enum、array、map 或 fixed。

标签: javascript node.js json recursion transformation


【解决方案1】:

我们可以使用归纳推理来分解它。下面的编号点对应代码中编号的 cmets -

  1. 如果输入 t 为 null,则返回一个空对象
  2. (感应)t 不为空。如果t.type 是一个对象,则transform 每个叶子和总和为一个对象
  3. (归纳)t 不为空,t.type 不是对象。如果t.fields 是一个对象,则transform 每个叶子,分配给{ [name]: ... },并求和成单个属性 对象
  4. (归纳)t 不为空,t.type 不是对象,t.fields 不是对象。返回关键字
const transform = t =>
  t === "null"
    ? {}                           // <- 1
: isObject(t.type)
    ? arr(t.type)                  // <- 2
        .map(transform)
        .reduce(assign, {})
: isObject(t.fields)
    ? { propertries:               // <- 3
          arr(t.fields)
            .map(v => ({ [v.name]: transform(v) }))
            .reduce(assign, {})
      }
: { type: "keyword" }              // <- 4

有几个助手可以避免复杂性 -

const assign = (t, u) =>
  Object.assign(t, u)

const arr = t =>
  Array.isArray(t) ? t : [t]
  
const isObject = t =>
  Object(t) === t

只需运行transform -

console.log(transform(input))

展开下面的sn-p,在浏览器中验证结果-

const assign = (t, u) =>
  Object.assign(t, u)

const arr = t =>
  Array.isArray(t) ? t : [t]
  
const isObject = t =>
  Object(t) === t

const transform = t =>
  t === "null"
    ? {}
: isObject(t.type)
    ? arr(t.type)
        .map(transform)
        .reduce(assign, {})
: isObject(t.fields)
    ? { propertries:
          arr(t.fields)
            .map(v => ({ [v.name]: transform(v) }))
            .reduce(assign, {})
      }
: { type: "keyword" }

const input =
  {name: "animal", type: ["null", {type: "record", name: "zooAnimals", fields: [{name: "color", type: ["null", "string"], default: null}, {name: "skinType", type: ["null", "string"], default: null}, {name: "species", type: {type: "record", name: "AnimalSpecies", fields: [{name: "terrestrial", type: "string"}, {name: "aquatic", type: "string"}]}}, {name: "behavior", type: ["null", {type: "record", name: "AnimalBehaviors", fields: [{name: "sound", type: ["null", "string"], default: null}, {name: "hunt", type: ["null", "string"], default: null}]}], default: null}]}]}

console.log(transform(input))

输出 -

{
  "propertries": {
    "color": {
      "type": "keyword"
    },
    "skinType": {
      "type": "keyword"
    },
    "species": {
      "propertries": {
        "terrestrial": {
          "type": "keyword"
        },
        "aquatic": {
          "type": "keyword"
        }
      }
    },
    "behavior": {
      "propertries": {
        "sound": {
          "type": "keyword"
        },
        "hunt": {
          "type": "keyword"
        }
      }
    }
  }
}

没有好处

第 2 步中,我们可以有一个复杂的 type,例如 -

{ name: "foo"
, type: [ "null", { obj1 }, { obj2 }, ... ]
, ...
}

在这种情况下,obj1obj2 可以每个 transform 成为一个 { properties: ... } 对象。使用.reduce(assign, {}) 意味着obj1 的属性将被obj2 的属性覆盖-

: isObject(t.type)
    ? arr(t.type)
        .map(transform)
        .reduce(assign, {})   // <- cannot simply use `assign`

为了解决这个问题,我们更智能地将步骤 2 更改为 merge 复杂类型 -

: isObject(t.type)
    ? arr(t.type)
        .map(transform)
        .reduce(merge, {})   // <- define a more sophisticated merge

merge 可能类似于 -

const merge = (t, u) =>
  t.properties && u.properties // <- both
    ? { properties: Object.assign(t.properties, u.properties) }
: t.properties                 // <- only t
    ? { properties: Object.assign(t.properties, u) }
: u.properties                 // <- only u
    ? { properties: Object.assign(t, u.properties) }
: Object.assign(t, u)          // <- neither

或相同的merge,但使用不同的逻辑方法 -

const merge = (t, u) =.
  t.properties || u.properties    // <- either
    ? { properties:               
          Object.assign
            ( t.properties || t
            , u.properties || u
            )
      }
    : Object.assign(t, u)         // <- neither

【讨论】:

  • 干得好,一如既往。有趣的是,我们做出了不同的选择来表明我们的不同案例。我的可以很好地使用一些辅助功能(发布得太接近就寝时间了!)。但是也有一些根本的区别——你将每个元素的结果合并到一个 type 数组中,我只取第二个。我对 ARVO 架构一无所知,但从这个示例中,我没有留下深刻的印象。
  • 谢谢你,很棒的解决方案!
  • @ScottSauyet 我同意你的观点。我创建了示例,以便问题不会变得太长,并且足够简短,足以涵盖我最重要的可能案例。感谢您抽出宝贵时间;)另外,AVRO 规范是一团糟,而且比我认为的要冗长。
  • @谢谢,如果字段数组的记录具有附加属性,例如{"data_type: { "type" : "animal"}},并且您的归纳原因 4 是基于此对象的,该怎么办?那么代码如何变化呢?
【解决方案2】:

我不知道您的输入格式,也不知道您的输出格式。所以这可能是不完整的。但是,它捕获了您的示例案例,并且可以作为您可以添加子句/条件的基线:

const convertField = ({name, type, fields}) =>
  Array .isArray (type) && type [0] === 'null' && type [1] === 'string'
    ? [name, {type: 'keyword'}]
  : Array .isArray (type) && type [0] === 'null' && Object (type [1]) === type [1]
    ? [name, {properties: Object .fromEntries (type [1] .fields .map (convertField))}]
  : Object (type) === type
    ? [name, {properties: Object .fromEntries (type .fields .map (convertField))}]
  : // else 
      [name, {type: 'keyword'}]

const convert = (obj) =>
  convertField (obj) [1]

const input = {name: "animal", type: ["null", {type: "record", name: "zooAnimals", fields: [{name: "color", type: ["null", "string"], default: null}, {name: "skinType", type: ["null", "string"], default: null}, {name: "species", type: {type: "record", name: "AnimalSpecies", fields: [{name: "terrestrial", type: "string"}, {name: "aquatic", type: "string"}]}}, {name: "behavior", type: ["null", {type: "record", name: "AnimalBehaviors", fields: [{name: "sound", type: ["null", "string"], default: null}, {name: "hunt", type: ["null", "string"], default: null}]}], default: null}]}]}

console .log (convert (input))
.as-console-wrapper {min-height: 100% !important; top: 0}

辅助函数convertField 将输入的一个字段转换为[name, &lt;something&gt;] 格式,其中&lt;something&gt;type 属性的结构而异。在两种情况下,我们使用这些结构的数组作为Object .fromEntries 的输入来创建对象。

主函数convert 只是从在根上调用convertField 的结果中获取第二个属性。如果整个结构总是像本例中那样开始,则此方法有效。

请注意,其中两个子句(第一个和第四个)的结果是相同的,而另外两个则非常相似。第一个和第二个子句的测试也非常接近。所以可能有一些合理的方法来简化这一点。但由于匹配测试与匹配输出不能很好地对齐,它可能不会是微不足道的。

您可以很容易地添加其他条件和结果。事实上,我最初是把最后两行替换成这样的:

  : type === 'string'
    ? [name, {type: 'keyword'}]
  : // else 
      [name, {type: 'unknown'}]

它可以更好地显示添加其他子句的位置,如果您错过了一个案例,还会在结果中添加一个符号 (unknown)。

【讨论】:

  • 这是一个很难处理的输入,尤其是{ type: string | array | object }。很好的电话{ type: "unknown" }
  • @Thankyou:当然,问题是该示例仅显示了输入可能复杂性的一小部分。
  • 没错!这个问题刚刚在接受的门槛之内,但仍有许多未定义的方面......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-20
  • 2021-09-22
  • 2021-09-16
相关资源
最近更新 更多