如何使用 Ramda 更新 JSON 中任何级别的键值?

How to update value of a key present at any level in a JSON using Ramda?

有没有办法找到一个键的值,它可以出现在任何对象下的任何级别并使用 Ramda 更新它?

例如,

JSON-1

{
  "query": {
    "boundary": {
      "type": "polygon",
      "coordinates": "[[-85.33604,35.055749],[-85.33604,35.07499772909699],[-85.279134,35.07499772909699],[-85.279134,35.055749],[-85.33604,35.055749]]"
    }
}

JSON-2

{
  "query": {
    "nearby": {
      "radius": "30mi",
      "coordinates": "[-121.40019800,38.55378300]"
    }
  }
}

在这两个 JSON 中,我想做类似的事情:

query.nearby.coordinates = JSON.parse(query.nearby.coordinates)

query.boundary.coordinates = JSON.parse(query.boundary.coordinates)

具有单一功能。

一种方法是遍历对象树,并尝试将找到的任何字符串解析为 JSON。

const parseNestedJSON = R.cond([
   [R.is(String), R.tryCatch(JSON.parse, R.nthArg(1))],
   [R.is(Object), obj => R.map(parseNestedJSON, obj)],
   [R.T, R.identity],
])

请注意,这可能会进行一些不需要的转换,例如将 {foo: '1'} 转换为 {foo: 1}(字符串到数字)。

只定位名为 coordinates:

的嵌套键可能更安全
const parseNestedJSON = R.cond([
    [R.has('coordinates'), R.over(R.lensProp('coordinates'), JSON.parse)],
    [R.is(Object), obj => R.map(parseNestedJSON, obj)],
    [R.T, R.identity],
])

编辑: 如果 coordinates 可能不是 json,你也可以在这里使用 tryCatch

  [R.has('coordinates'), R.over(R.lensProp('coordinates'), R.tryCatch(JSON.parse, R.nthArg(1)))]

用法:

parseNestedJSON({"nearby": {"coordinates": "[-121.40019800,38.55378300]"}})
=> { nearby: { coordinates: [ -121.400198, 38.553783 ] } }

另一种选择是定义一个可以负责更新值的镜头。

第一种方法假设可以找到坐标的已知路径数量有限。

// Creates a lens that accepts a list of paths and chooses the first
// matching path that exists in the target object

const somePathLens = paths => toFunctor => target => {
  const path = R.find(
    p => R.pathSatisfies(x => x != null, p, target),
    paths
  )
  return R.map(
    value => R.assocPath(path, value, target),
    toFunctor(R.path(path, target))
  )
}

// R.over can then be used with JSON.parse to parse the first
// matching path that is found.

const parseCoords = R.over(
  somePathLens([
    ['query', 'boundary', 'coordinates'],
    ['query', 'nearby', 'coordinates']
  ]),
  JSON.parse
)

console.log(parseCoords({
  "query": {
    "boundary": {
      "type": "polygon",
      "coordinates": "[[-85.33604,35.055749],[-85.33604,35.07499772909699],[-85.279134,35.07499772909699],[-85.279134,35.055749],[-85.33604,35.055749]]"
    }
  }
}))

console.log(parseCoords({
  "query": {
    "nearby": {
      "radius": "30mi",
      "coordinates": "[-121.40019800,38.55378300]"
    }
  }
}))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>

如果实际路径未知并且您只需要通过给定键找到第一个值,则可以使用第二种方法。

// Recursively search for the given key in an object, returning the
// first matching path if found.

const findKeyInPath = (keyToFind, obj) => {
  const findKeyInPath_ = o =>
    R.has(keyToFind, o)
      // if found, return this key as the path
      ? [keyToFind]
      // otherwise find all keys with objects and recursively
      // call this function.
      : R.reduceRight((k, acc) => {
          // find either the subpath of this key, or the subpath
          // found in the remaining keys
          const subPath = R.when(R.isEmpty, _ => acc, findKeyInPath_(o[k]))
          // if the subpath contains a key, prepend it with the
          // current key, otherwise return the empty list
          return R.unless(R.isEmpty, R.prepend(k), subPath)
        }, [], R.filter(k => R.propIs(Object, k, o), R.keys(o)))
  return findKeyInPath_(obj)
}

// Create a lens that recursively searches for the first matching
// key within a target object.

const someKeyLens = key => toFunctor => target => {
  // find the path using our new `findKeyInPath` function
  const path = findKeyInPath(key, target)
  return R.map(
    value => R.assocPath(path, value, target),
    toFunctor(R.path(path, target))
  )
}

const parseCoords = R.over(
  someKeyLens('coordinates'),
  JSON.parse
)

console.log(parseCoords({
  "query": {
    "boundary": {
      "type": "polygon",
      "coordinates": "[[-85.33604,35.055749],[-85.33604,35.07499772909699],[-85.279134,35.07499772909699],[-85.279134,35.055749],[-85.33604,35.055749]]"
    }
  }
}))

console.log(parseCoords({
  "query": {
    "nearby": {
      "radius": "30mi",
      "coordinates": "[-121.40019800,38.55378300]"
    }
  }
}))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>

值得一提的是,只有在保证在目标对象中找到路径的情况下,这些才是有效的镜头,否则行为是不确定的。