【问题标题】:Recursive function to loop an object and set value, with an array of nested properties to join objects循环对象并设置值的递归函数,使用嵌套属性数组连接对象
【发布时间】:2021-11-23 23:12:47
【问题描述】:

我有多个 json5 文件需要加入一个对象。

其他文件将加入的主要对象示例: /equations/mass-energy-equivalence.json5

{
    name: 'Mass-energy equivalence',
    expression: 'E=mc^{2}',
    expressionIntern: '\\mag{E}=\\mag{m}\\const{c}^{2}',
    description: '...',
    categories: ['physics'],
    units: [
        'joule'
    ],
    constants: [
        'speed-of-light'
    ],
    magnitudes: [
        'energy', 'mass'
    ],
    values: [
        { value: 1000, units: ['joule'] }
    ]
}

/magnitudes/energy.json5

{ name: 'Energy', symbol: 'E', slug: 'energy', units: ['joule'],  description: '', ... }

所以幅度有单位,我必须加入units/joulemagnitudes/energy,最后加入第一个对象。 /units/joule.json5

{ name: 'Joule', symbol: 'J', slug: 'joule', description: '', ... }

等等。

我需要加入:类别、单位、常数和幅度。就像 MySQL 加入一样。幅度也有单位,所以它们也必须加入。 所以我正在尝试做一个函数来获取这样的嵌套属性数组:

这是函数的输入:

const nestedProperties = [
    'categories.slug',
    'constants.slug.units.slug',
    'magnitudes.slug',
    'units.slug',
    'variables.slug.units.slug',
    'values.units.slug'
];

需要为所有nestedProperties 执行此操作。 最终的对象将是输出https://i.stack.imgur.com/yat2V.jpg

我有一个函数可以获取数据await getData(),所以我需要这个递归函数来设置data.categories = await getData('categories', slug: 'physics')

我的想法是这样的……但还没有完成。

getAllData(object, nestedProperties) {
    nestedProperties.forEach(async (item) => {
        const parts = item.split('.');
        const size = parts.length;
        // We need at least two parts to get the data.
        if(size === 0) console.error('Invalid Path');
        // Size is even so is a multiple of 2
        // ex. categories.slug
        if(size % 2 === 0) {
            for(let i = 0; i < size - 2; i += 2) {
                // path.property -> categories.slug
                if(i == 0) {
                    let path = parts[i];
                    let property = parts[i + 1];
                    // If the path is in the data and is an array with items
                    // ex. data[categories]
                    if(Array.isArray(data[path]) && data[path].length > 0) {
                        // Iterate
                        for(let i = 0; i < data[path].length; i++) {
                            // path => 'categories',
                            // data[path] => 'physics'
                            data[path] = getData(path, data[path]);
                            // Recursive data[path] = getAllData(path, data[path])
                            
                        }
                    } else {
                        data[path] = getAllData(path, data[path])
                    }
                }
            }
        } else {
            // Is odd so we need to do it a bit different
            // ex. 'values.units.slug'
        }
    }
}

尝试了很多但没有成功获得 2 或 3 嵌套属性:[ 非常感谢。

【问题讨论】:

  • 澄清nestedProperties是如何产生的
  • 示例:'categories.slug' 类别是文件所在的路径,而 slug 是我获取文件的属性。所以这表示:categories/physics.json5 from:javascript { name: 'Mass-energy equivalence', ... categories: ['physics'], 它可以是字符串或数组。就像mysql的外键一样
  • 我认为您在这里遗漏了一些信息。 "variables" 在哪里使用?什么是 "symbol""description""type",它们来自哪里?您的第一个 JSON 样本是输入,最后一个是输出吗? "constants.slug.units.slug" 如何在示例输出中表示?
  • 另外,getData('categories', slug: 'physics') 不是合法的语法。你的意思是像getData('categories', {slug: 'physics'}) (注意花括号。)或者它应该是不同的东西?
  • 我明白了。有categories/physics.json5 (categories.slug)、constants/speed-of-light.json5 (constants.slug)、units/a.json5, b.json5, etc..、(units.slug) 的文件。因此,nestedProperties 数组表示我如何以及从何处获取第一个(输入)要加入的其他文件。第一个是将从其他 json 文件获取所有数据的对象,是的,最后一个是 otuput,如何第一个 json 将与所有其他 json 文件一起查看。

标签: javascript json function recursion


【解决方案1】:

恐怕我现在没有时间对此进行更完整的解释,所以我会简短一些。如果我明天有时间,我会添加更多解释。它不完整,并且无法处理您的'constants.slug.units.slug',因此它可能真的很遥远。 (实际上我只是简单地删除了 slug 节点,因为我不明白它们。)

getData 只是一个假人,旨在让我们运行类似于您上面的getData 的东西。 (这是否相当于$content from your code?)。

last 是一个获取数组最后一个元素的简单助手。

getPath 采用['foo', 1' 'bar'] 等路径和{foo: [{bar: 1, baz: 2}, {bar: 3, baz: 4}], qux: 5} 等对象并返回3,即索引@987654336 处元素的bar 属性的值我们对象的foo 属性的@。

setPath 只是反过来:

setPath (['foo', 1, 'bar']) (42) ({foo: [{bar: 1, baz: 2}, {bar: 3, baz: 4}], qux: 5})
//=> {foo: [{bar: 1, baz: 2}, {bar: 42, baz: 4}], qux: 5}

fullPaths 更复杂。它处理您的字段可能是数组或标量值的事实。它采用诸如 ['magnitudes'] 之类的路径和您的初始数据,并以getPathsetPath 所需的格式查找路径。因此

fullPaths (['magnitudes']) (rawData) //=> [["magnitudes", 0], ["magnitudes", 1]]

分别指向'energy''mass'

有了这些助手,我们可以写 getAllData

在将您的nestedProperties 转换为数组后使用fullPaths,删除'slug' 子字符串。有了这些结果,我们可以深入研究,比如说values.units.slug 得到['values', 0, 'units', 0],它映射到'joules',并使用'units''joules',我们称之为getData

在 Promises 返回 resolve 后,我们可以折叠结果,调用诸如 setPath (['values', 0, 'units', 0], promiseResult, accumulator) 之类的东西。我们返回折叠的结果。

我不知道我是否有很多时间来讨论这个问题,但如果我这样做了,我很想知道这与您的要求有多接近。例如,我不清楚您是否需要对从getData 返回的每个结果运行相同的getAllData,如果这样做,是否要为它们使用相同的nestedProperties

我也不知道如何处理constants.slug.units.slug,因为我们的常量是字符串值并且没有单位。

const last = (xs) => 
  xs [xs .length - 1]

const getPath = ([p, ...ps]) => (o) =>
  p == undefined ? o : getPath (ps) (o && o[p])

const setPath = ([p, ...ps]) => (v) => (o) =>
  p == undefined ? v : Object .assign (
    Array .isArray (o) || Number .isInteger (p) ? [] : {},
    {...o, [p]: setPath (ps) (v) ((o || {}) [p])}
  )

const fullPaths = ([p, ...ps]) => (o) => 
  p == undefined 
    ? [[]]
  : Array .isArray (o)
     ? o .flatMap ((x, i) => fullPaths (ps) (x [p]) .map (ns => [p, i, ...ns]))
  : Object (o) === o
     ? p in o
       ? Array .isArray (o [p])
         ? o [p] .map ((x, i) => fullPaths (ps) (x) .flatMap ((x) => [p, i, ...x]))
         : fullPaths (ps) (o [p]) .map (x => [p, ...x])
     : []
  : [[p]]

const getAllData = (
  rawData, 
  nestedProperties, 
  paths = nestedProperties .map (s => s.split ('.')) 
                           .map (a => a.filter (s => s !== 'slug'))
                           .flatMap (p => fullPaths (p) (rawData))
) => 
  Promise .all (
    paths .map (
      p => getData (
        last (p .filter (s => String (s) === s)), 
        getPath (p) (rawData)
      )
    )
  ) .then (res => res .reduce (
    (a, r, i) => setPath (paths[i]) (r) (a), 
    rawData 
  ))

const rawData = {name: 'Mass-energy equivalence', expression: 'E=mc^{2}', expressionIntern: '\\mag{E}=\\mag{m}\\const{c}^{2}', description: '...', categories: ['physics'], units: ['joule'], constants: ['speed-of-light'],  magnitudes: ['energy', 'mass'], values: [{value: 1000, units: ['joule']}]}
const nestedProperties = ['categories.slug',   /*'constants.slug.units.slug',*/ 'magnitudes.slug', 'units.slug', 'variables.slug.units.slug', 'values.units.slug']

getAllData (rawData, nestedProperties )
  .then ((r) => console .log(JSON.stringify(r, null, 4)))
  .catch (console .warn)
.as-console-wrapper {max-height: 100% !important; top: 0}
<script> <!-- Dummy version of getData -->
const getData = ((data) => async (group, value) => group in data && value in data [group] ? Promise .resolve (data [group] [value]) : Promise .reject (`Cannot find ${group}/${value}`))({categories: {physics: {id: 2, name: "Physics", description: "Physics (from Ancient Greek: φυσική (ἐπιστήμη), romanized: physikḗ (epistḗmē), lit. 'knowledge of nature', from φύσις phýsis 'nature') is the natural science that studies matter, its motion and behavior through space and time, and the related entities of energy and force. Physics is one of the most fundamental scientific disciplines, and its main goal is to understand how the universe behaves."}, chemistry: {id: 3, name: "Chemistry", description: "Chemistry is the scientific discipline involved with elements and compounds composed of atoms, molecules and ions: their composition, structure, properties, behavior and the changes they undergo during a reaction with other substances."}}, units: {joule: {name: "Joule", symbol: {text: "J", html: "J", tex: "J"}, type: "si", categories: ["physics"], units: ["joule-per-kelvin", "joule-second"], description: "The joule (/dʒaʊl,  dʒuːl/ jowl,  jool) is a derived unit of energy in the International System of Units. It is equal to the energy transferred to (or work done on) an object when a force of one newton acts on that object in the direction of the force's motion through a distance of one metre (1 newton metre or $N⋅m$). It is also the energy dissipated as heat when an electric current of one ampere passes through a resistance of one ohm for one second. It is named after the English physicist James Prescott Joule (1818–1889)."}}, magnitudes: {energy: {name: 'Energy', symbol: {text: 'E', html: 'E', tex: 'E',}, categories: ['physics'], description: 'In physics, energy is the quantitative property that must be transferred to an object in order to perform work on, or to heat, the object. Energy is a conserved quantity; the law of conservation of energy states that energy can be converted in form, but not created or destroyed. The SI unit of energy is the joule, which is the energy transferred to an object by the work of moving it a distance of 1 metre against a force of 1 newton.', baseUnit: 'joule', units: ['joule']}, mass: {name: "Mass", symbol: {text: "m", html: "m", tex: "m"}, categories: ["physics"], description: "Property of matter to resist changes of the state of motion and to attract other bodies", baseUnit: "kilogram", units: ["tonne", "kilogram", "gram", "milligram", "microgram", "long-ton", "short-ton", "stone", "pound", "ounce"]}}, constants: {'speed-of-light': {name: "Speed of light in vacuum", symbol: {text: "c", html: "c", tex: "c"}, description: "The speed of light in vacuum, commonly denoted $c$, is a universal physical constant important in many areas of physics. Its exact value is defined as $299, 792, 458$ $m/s$ (approximately $300, 000$ $km/s$,  or $18, 6000$ $mi/s$). It is exact because, by international agreement, a metre is defined as the length of the path travelled by light in vacuum during a time interval of $\\frac{1}{299, 792, 458}$ second. According to special relativity, $c$ is the upper limit for the speed at which conventional matter, energy or any information can travel through coordinate space.", categories: ["universal", "physics"], units: ["metre-per-second"], values: [{value: 299792458, units: "metre-per-second", exact: false, base: false}, {value: 3e8, units: "metre-per-second", exact: false}]}}})
</script>

【讨论】:

  • 嘿斯科特!,我真的很感激。是的,我使用$content() 来获取数据,并将getData() 替换为它。关于对结果重新使用getAllData,是的,我必须再次使用相同的函数,因为结果也有nestedProperties 作为categoriesunits。我将在这周内分析您的代码并进行一些测试,然后我会向您报告!
  • 有关getPathsetPath 的更多信息可以在我的其他答案中找到。 last 是微不足道的。 getData 无趣。真正的工作在findPaths,当然还有getAllData
  • 如果你想在我构建它的 REPL 中使用它,它位于link.sauyet.com/32
猜你喜欢
  • 2021-05-19
  • 1970-01-01
  • 2019-11-23
  • 2021-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-09
相关资源
最近更新 更多