JavaScript:从一种嵌套 JSON 格式转换为另一种嵌套 JSON 格式
JavaScript: Transform from one nested JSON format to another nested JSON format
我正在尝试将 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
字段的类型可以是 Array
或 Map
,如 behavior
与 species
所示,其中行为具有数组,物种具有一张地图。
如果您一定要看到我做了我的试错,这是我的代码,但我没有任何用处:
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
});
我不知道你的输入格式,也不知道你的输出格式。所以这可能是不完整的。不过,它捕获了您的示例案例,并且可以作为您可以添加 clauses/conditions:
的基线
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, <something>]
格式,其中 <something>
因 type
属性。在两种情况下,我们使用这些结构的数组作为 Object .fromEntries
的输入以创建一个对象。
主函数 convert
只是从对根调用的 convertField
的结果中获取第二个 属性。如果整体结构总是像本例中那样开始,那将起作用。
请注意,其中两个子句(第一个和第四个)的结果是相同的,另外两个非常相似。此外,对第一条和第二条的测试非常接近。所以可能有一些合理的方法来简化这个。但是因为匹配测试与匹配输出不一致,所以它可能不是微不足道的。
您可以很容易地添加其他条件和结果。事实上,我最初写它的最后两行是这样的:
: type === 'string'
? [name, {type: 'keyword'}]
: // else
[name, {type: 'unknown'}]
它可以更好地显示在何处添加您的其他子句,并且如果您遗漏了一个案例,还会在结果中添加一个符号 (unknown
)。
我们可以使用归纳推理来分解它。下面的编号点对应代码中编号的注释 -
- 如果输入
t
为空,return 一个空对象
- (归纳)
t
不为空。如果t.type
是一个对象,transform
每个叶子和总和成为一个对象
- (归纳)
t
不为空且 t.type
不是对象。如果t.fields
是一个对象,transform
每片叶子,赋值给{ [name]: ... }
,求和成单个properties对象
- (归纳)
t
不为空,t.type
不是对象,t.fields
不是对象。 Return 关键字.
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))
展开下面的代码片段以在浏览器中验证结果 -
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 }, ... ]
, ...
}
在这种情况下,obj1
和 obj2
可能每个 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
我正在尝试将 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
字段的类型可以是 Array
或 Map
,如 behavior
与 species
所示,其中行为具有数组,物种具有一张地图。
如果您一定要看到我做了我的试错,这是我的代码,但我没有任何用处:
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
});
我不知道你的输入格式,也不知道你的输出格式。所以这可能是不完整的。不过,它捕获了您的示例案例,并且可以作为您可以添加 clauses/conditions:
的基线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, <something>]
格式,其中 <something>
因 type
属性。在两种情况下,我们使用这些结构的数组作为 Object .fromEntries
的输入以创建一个对象。
主函数 convert
只是从对根调用的 convertField
的结果中获取第二个 属性。如果整体结构总是像本例中那样开始,那将起作用。
请注意,其中两个子句(第一个和第四个)的结果是相同的,另外两个非常相似。此外,对第一条和第二条的测试非常接近。所以可能有一些合理的方法来简化这个。但是因为匹配测试与匹配输出不一致,所以它可能不是微不足道的。
您可以很容易地添加其他条件和结果。事实上,我最初写它的最后两行是这样的:
: type === 'string'
? [name, {type: 'keyword'}]
: // else
[name, {type: 'unknown'}]
它可以更好地显示在何处添加您的其他子句,并且如果您遗漏了一个案例,还会在结果中添加一个符号 (unknown
)。
我们可以使用归纳推理来分解它。下面的编号点对应代码中编号的注释 -
- 如果输入
t
为空,return 一个空对象 - (归纳)
t
不为空。如果t.type
是一个对象,transform
每个叶子和总和成为一个对象 - (归纳)
t
不为空且t.type
不是对象。如果t.fields
是一个对象,transform
每片叶子,赋值给{ [name]: ... }
,求和成单个properties对象 - (归纳)
t
不为空,t.type
不是对象,t.fields
不是对象。 Return 关键字.
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))
展开下面的代码片段以在浏览器中验证结果 -
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 }, ... ]
, ...
}
在这种情况下,obj1
和 obj2
可能每个 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