【问题标题】:How to Update object values if anything changes , if not return same object?如果有任何变化,如何更新对象值,如果不返回相同的对象?
【发布时间】:2020-09-09 11:13:29
【问题描述】:

这里我有一个对象,即 ApiData1 。它在 properties 中有颜色键值对。我正在根据 ApiData2 值 numberOfProjects 更改颜色值,并且有一个 numberOfProjects 值位于一组范围之间的范围,我正在更新颜色值。它工作正常。

在某些情况下,ApiData2 值会为 null。在这种情况下,它必须返回已经存在的默认值,即 ApiData1 中存在的值。但根据我的代码,它删除了 value 。我不知道如何解决这个问题。请帮我解决这个问题。我在这里分享工作演示链接JS_FIDDLE

let ApiData1 = {
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "geometry": {
                "type": "MultiPolygon"
            },
            "properties": {
                "color": 1,
                "id": 10,
                "stateId": 10,
                "name": "Tamil Nadu",
                "code": "TN"
            }
        },
        {
            "type": "Feature",
            "geometry": {
                "type": "MultiPolygon"
            },
            "properties": {
                "color": 1,
                "id": 11,
                "stateId": 11,
                "name": "Karnataka",
                "code": "KA"
            }
        },
        {
            "type": "Feature",
            "geometry": {
                "type": "MultiPolygon"
            },
            "properties": {
                "color": 1,
                "id": 12,
                "stateId": 12,
                "name": "Pondicherry",
                "code": "PY"
            }
        },
         {
            "type": "Feature",
            "geometry": {
                "type": "MultiPolygon"
            },
            "properties": {
                "color": 6,
                "id": 13,
                "stateId": 13,
                "name": "Maharashtra",
                "code": "TT"
            }
        },

    ]
}

let ApiData2 = [
    {
        id: 10,
        name: "Tamil Nadu",
        code: "TN",
        latitude: 29.9964948,
        longitude: 81.6855882,
        latestMetric: {
            stateId: 10,
            year: 0,
            numberOfProjects: 1433,
        }
    },
    {
        id: 11,
        name: "Karnataka",
        code: "KA",
        latitude: 21.9964948,
        longitude: 82.6855882,
        latestMetric: {
            stateId: 11,
            year: 0,
            numberOfProjects: 3500,
        }
    },
    {
        id: 12,
        name: "Pondicherry",
        code: "PY",
        latitude: 22.9964948,
        longitude: 87.6855882,
        latestMetric: {
            stateId: 12,
            year: 0,
            numberOfProjects: 5500,
        }
    },
    {
        id: 13,
        name: "Maharashtra",
        code: "PY",
        latitude: 22.9964948,
        longitude: 87.6855882,
        latestMetric: null
    }
];


function updateColor() {  
     function updateProperties(colorJsonObject, colorValue) {
        let updatedProperties = {
            ...colorJsonObject.properties,
            color: colorValue
        };
        /* console.log(updatedProperties) */
        return updatedProperties;

    }

    let range = [
        {
            "Minimum": 1,
            "Maximum": 2000,
            "color": 1
        },
        {
            "Minimum": 2000,
            "Maximum": 4000,
            "color": 2
        },
        {
            "Minimum": 4000,
            "Maximum": 6000,
            "color": 3
        }
    ]

    let newData = {
       ...ApiData1,
       features: ApiData1.features.map(colorObject => {
           const apiData = ApiData2.find(apiData => {
            if (
                colorObject.properties.stateId === apiData.latestMetric.stateId
            ) {
                return true;
            }
            return false;
          });
          console.log(apiData)
          let newValue;
          range.forEach(i => {
                    if (
                        apiData.latestMetric.numberOfProjects >= i.Minimum &&
                        apiData.latestMetric.numberOfProjects <= i.Maximum
                    ) {

                            let value = updateProperties(colorObject, i.color)
                        newValue = {...colorObject,properties:value}
                    }
                });
           return newValue;
       })
    }


    return newData;
}

let colorValue = updateColor();

 console.log(colorValue) 

非常感谢您的帮助或建议。

提前致谢。

结果:

在 ApiData1 中,马哈拉施特拉邦的颜色值为 4 。在 ApiData2 中,马哈拉施特拉邦的 latestMetric 为空。如果我在 ApiData1 和 ApiData2 中删除此 Maharashtra 值。代码工作正常并更新颜色值。但是如果我在这种情况下运行代码,它会破坏代码。

我想要做的是,如果 ApiData2 值返回 null,我只需要传递 ApiData1 中已经存在的默认值而不更新它。输出必须是这样的

预期输出:

{
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "geometry": {
                "type": "MultiPolygon"
            },
            "properties": {
                "color": 1,
                "id": 10,
                "stateId": 10,
                "name": "Tamil Nadu",
                "code": "TN"
            }
        },
        {
            "type": "Feature",
            "geometry": {
                "type": "MultiPolygon"
            },
            "properties": {
                "color": 2,
                "id": 11,
                "stateId": 11,
                "name": "Karnataka",
                "code": "KA"
            }
        },
        {
            "type": "Feature",
            "geometry": {
                "type": "MultiPolygon"
            },
            "properties": {
                "color": 3,
                "id": 12,
                "stateId": 12,
                "name": "Pondicherry",
                "code": "PY"
            }
        },
         {
            "type": "Feature",
            "geometry": {
                "type": "MultiPolygon"
            },
            "properties": {
                "color": 6,
                "id": 13,
                "stateId": 13,
                "name": "Maharashtra",
                "code": "TT"
            }
        },

    ]
}

【问题讨论】:

  • 我正在研究与此类似的东西。我会尽快为你写一个答案。您能否更新问题以提供您正在寻找的确切输出?
  • 我已经编辑了我的问题。请看一下
  • 通过 ApiData2 值 null 你的意思是整个 ApiData2 数组将为空?
  • 没有。如果您在 ApiData2 中看到第四个对象,则 latestMetric 为空。在这种情况下,ApiData1 值,即第四个对象将保持不变。

标签: javascript arrays reactjs javascript-objects


【解决方案1】:

这段代码有很多需要修复的地方,所以我从粗略的开始 -

// pass1.js

function updateColor()
{ let range = [
    {
      "Minimum": 1,
      "Maximum": 2000,
      "color": 1
    },
    {
      "Minimum": 2000,
      "Maximum": 4000,
      "color": 2
    },
    {
      "Minimum": 4000,
      "Maximum": 6000,
      "color": 3
    }
  ]

  function updateProperties(feature, colorValue)
  { return {
      ...feature.properties,
      color: colorValue
    };
  }

  let newData = {
    ...ApiData1,
    features: ApiData1.features.map(feature => {
      const data2 = ApiData2.find(x =>
        feature.properties.stateId === x.latestMetric.stateId
      )
      let newValue
      range.forEach(i => {
        let value
        if (
          data2.latestMetric.numberOfProjects >= i.Minimum &&
          data2.latestMetric.numberOfProjects <= i.Maximum
        ) {
          value = updateProperties(feature, i.color)
          newValue = {...feature,properties:value}
        }
      })
      return newValue
    })
  }

  return newData
}

错误 1: 不安全的深层属性访问 -

function updateColor()
{ let range = // ...

  function updateProperties // ...

  let newData = {
    ...ApiData1,
    features: ApiData1.features.map(feature => {
      const data2 = ApiData2.find(x =>
        //
        // Bug!
        // TypeError: Cannot read property 'stateId' of null
        // ApiData2 = [ ...
        //   {
        //       id: 13,
        //       name: "Maharashtra",
        //       code: "PY",
        //       latitude: 22.9964948,
        //       longitude: 87.6855882,
        //       latestMetric: null
        //   }
        //
        feature.properties.stateId === x.latestMetric.stateId
      )

      // ...
    })
  }

  return newData
}

在尝试深度属性访问之前,您必须进行空检查 -

const data2 = ApiData2.find(x =>
  feature.properties && x.latestMetric && // <--
  feature.properties.stateId === x.latestMetric.stateId
)

错误 2find 有时会返回 undefined -

function updateColor()
{ let range = // ...

  function updateProperties // ...

  let newData = {
    ...ApiData1,
    features: ApiData1.features.map(feature => {
      // ...

      range.forEach(i => {
        let value
        if (
          //
          // Bug!
          // TypeError: Cannot read property 'latestMetric' of undefined
          // data2 is the result of `ApiData2.find(...)`
          // if an element is not found, `.find` returns undefined
          //
          data2.latestMetric.numberOfProjects >= i.Minimum &&
          data2.latestMetric.numberOfProjects <= i.Maximum
        ) // ...

      })
      // ...
    })
  }

  return newData
}

有时您必须在尝试(任何)属性访问之前进行空检查!

if (
  data2 && data2.latestMetric &&  // <--
  data2.latestMetric.numberOfProjects >= i.Minimum &&
  data2.latestMetric.numberOfProjects <= i.Maximum
)

太痛苦了

整个updateColor 函数写起来是不是感觉乏味而痛苦?让我们看看我们是否不能让整个过程变得更好一点。我们有两个核心问题 -

  1. 安全访问深度嵌套状态
  2. 安全(且不可变)更新深度嵌套状态

1.更好的null

// Util.js

import { Just, Nothing, fromNullable } from "data.maybe"

const safeProp = (o = {}, p = "") =>
  o == null
    ? Nothing()
    : fromNullable(o[p])

const safeProps = (o = {}, props = []) =>
  props.reduce
    ( (mr, p) => mr.chain(r => safeProp(r, p))
    , fromNullable(o)
    )

export { safeProp, safeProps }
safeProp(ApiData1, "type")
// Just {value: "FeatureCollection"}

safeProp(ApiData1, "zzz")
// Nothing {}

safeProps(ApiData, ["features", 0, "type"])
// Just {value: "Feature"}

safeProps(ApiData1, ["features", 0, "properties", "color"])
// Just {value: 1}

safeProps(ApiData1, ["features", 999, "properties", "color"])
// Nothing {}

Maybe 允许我们以更安全的方式处理可空值 -

safeProp(ApiData1, "type")
// Just {value: "FeatureCollection"}

safeProp(ApiData1, "type").getOrElse("not found!")
// FeatureCollection

safeProp(ApiData1, "zzz")
// Nothing {}

safeProp(ApiData1, "zzz").getOrElse("not found!")
// not found!

事情开始成形。现在让我们做一个更安全的find -

// Util.js

import // ...

const identity = x => x

const safeFind = (a = [], f = identity) =>
  fromNullable(a.find(f))

const safeProp // ...
const safeProps // ...

export { safeFind, //... }
// Main.js

import { safeFind, safeProp, safeProps } from "./Util"

const Api2FindByStateId = (q = null) =>
  safeFind
    ( ApiData2
    , x =>
        safeProps(x, ["latestMetric", "stateId"])
          .map(stateId => stateId === q)
          .getOrElse(false)
    )

function updateColor ()
{
  // ...

  return {
    ...ApiData1,
    features: ApiData1.features.map(feature => {

      const data2 =
        safeProps(feature, ["properties", "stateId"])
         .chain(Api2FindByStateId)

      // range.forEach()

    })
  }
}

我们不想在每次使用对象时都使用safeProp。它仅适用于形状不确定的物体。如果可能,我们希望编写我们可以依赖的具体对象。这是定义Range 模块的最佳时机-

// Range.js

const range = (min = 0, max = 0, data = null) =>
  ({ min: parse(min), max: parse(max), data })

const inRange = ({ min, max } = range(), x = 0) =>
  x >= min && x < max

const parse = (n) =>
  Number.isInteger(n) ? n : 0

export { range, inRange } // <-- export only what you plan to use

现在有了定义明确的Range 模块,我们就可以完成我们的程序了-

import { safeProp, safeProps } from './Util'
import { range, inRange } from './Range'

const ranges =
  [ range(1, 2000, { color: 1 })
  , range(2000, 4000, { color: 2 })
  , range(4000, 6000, { color: 3 })
  ]

const findRange = (q = 0) =>
  safeFind(ranges, r => inRange(r, q))

function updateColor ()
{ return {
    ...ApiData1,
    features: ApiData1.features.map(feature => {
      const defaultColor =
        safeProps(feature, ["properties", "color"])
          .getOrElse(0)

      const newColor =
        safeProps(feature, ["properties", "stateId"])
          .chain(Api2FindByStateId)
          .chain(data2 => safeProps(data2, ["latestMetric", "numberOfProjects"]))
          .chain(findRange)
          .chain(range => safeProp(range.data, "color"))
          .getOrElse(defaultColor)

      return { ...feature, properties: { ...feature.properties, color: newColor } }
    })
  }
}

console.log(JSON.stringify(updateColor(), null, 2)) // <-- run it

这是输出 -

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "geometry": {
        "type": "MultiPolygon"
      },
      "properties": {
        "color": 1,      // <--
        "id": 10,
        "stateId": 10,
        "name": "Tamil Nadu",
        "code": "TN"
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "MultiPolygon"
      },
      "properties": {
        "color": 2,      // <--
        "id": 11,
        "stateId": 11,
        "name": "Karnataka",
        "code": "KA"
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "MultiPolygon"
      },
      "properties": {
        "color": 3,      // <--
        "id": 12,
        "stateId": 12,
        "name": "Pondicherry",
        "code": "PY"
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "MultiPolygon"
      },
      "properties": {
        "color": 6,      // <--
        "id": 13,
        "stateId": 13,
        "name": "Maharashtra",
        "code": "TT"
      }
    }
  ]
}

如果您在查看chain 操作序列的运行方式时遇到问题,我们可以添加一个log 实用程序,它可以逐行显示我们程序的执行 -

const log = (label = "") =>
  x => (console.log(label, `->`, JSON.stringify(x)), Just(x))

function updateColor ()
{ return {
    ...ApiData1,
    features: ApiData1.features.map(feature => {
      const defaultColor =
        safeProps(feature, ["properties", "color"])
          .chain(log("feature.properties.color"))          // <-- log
          .getOrElse(0)

      const newColor =
        safeProps(feature, ["properties", "stateId"])
          .chain(log("feature.properties.stateId"))        // <-- log
          .chain(Api2FindByStateId)
          .chain(log("Api2FindByStateId"))                 // <-- log
          .chain(data2 => safeProps(data2, ["latestMetric", "numberOfProjects"]))
          .chain(log("data2.lastMetric.numberOfProjects")) // <-- log
          .chain(findRange)
          .chain(log("findRange"))                         // <-- log
          .chain(range => safeProp(range.data, "color"))
          .chain(log("range.data.color"))                  // <-- log
          .getOrElse(defaultColor)

      console.log(`newColor -> ${newColor}\n---`)          // <-- log
      return { ...feature, properties: { ...feature.properties, color: newColor } }
    })
  }
}

console.log(JSON.stringify(updateColor(), null, 2)) // <-- run it
feature.properties.color -> 1
feature.properties.stateId -> 10
Api2FindByStateId -> {"id":10,"name":"Tamil Nadu","code":"TN","latitude":29.9964948,"longitude":81.6855882,"latestMetric":{"stateId":10,"year":0,"numberOfProjects":1433}}
data2.lastMetric.numberOfProjects -> 1433
findRange -> {"min":1,"max":2000,"data":{"color":1}}
range.data.color -> 1
newColor -> 1
---
feature.properties.color -> 1
feature.properties.stateId -> 11
Api2FindByStateId -> {"id":11,"name":"Karnataka","code":"KA","latitude":21.9964948,"longitude":82.6855882,"latestMetric":{"stateId":11,"year":0,"numberOfProjects":3500}}
data2.lastMetric.numberOfProjects -> 3500
findRange -> {"min":2000,"max":4000,"data":{"color":2}}
range.data.color -> 2
newColor -> 2
---
feature.properties.color -> 1
feature.properties.stateId -> 12
Api2FindByStateId -> {"id":12,"name":"Pondicherry","code":"PY","latitude":22.9964948,"longitude":87.6855882,"latestMetric":{"stateId":12,"year":0,"numberOfProjects":5500}}
data2.lastMetric.numberOfProjects -> 5500
findRange -> {"min":4000,"max":6000,"data":{"color":3}}
range.data.color -> 3
newColor -> 3
---
feature.properties.color -> 6
feature.properties.stateId -> 13
newColor -> 6
---
{ "type": "FeatureCollection", "features": [ ... ] } 

特别注意最后一个feature马哈拉施特拉邦的日志输出。 Api2FindByStateId 没有输出,因为没有找到匹配项并且返回了 Nothing。我们看到newColor -&gt; 6,因为一旦遇到Nothing,就会跳过所有中间的chain 计算!


2。更好的update

这是我们想要避免的噩梦 -

return { ...feature, properties: { ...feature.properties, color: newColor } }

Immutable 之类的模块可以极大地帮助解决这个问题 -

import { fromJS, setIn } from "immutable"

function updateColor (feature = {})
{ const defaultColor = //...

  const newColor = // ...

  // immutable update
  return setIn(fromJS(feature), ["properties", "color"], newColor).toJS()
}

function updateColors()
{ return {
    ...ApiData1,
    features: ApiData1.features.map(updateColor)
  }
}

当您程序中的所有其他数据都在Immutable 保护伞下时,可以获得更大的收益。 docs 展示了许多有用的示例,它们应该可以帮助您了解如何有效地使用该库。

【讨论】:

  • 您的程序中的技术复杂性令人吃惊。我更新的答案试图提炼它,但仍有一些改进的地方。如果您有具体问题,请 lmk,我会在我腾出时间进行另一次更新时尝试回答:D
  • 嗨,非常感谢您付出的时间和精力。您能否与我分享我应该采用哪种解决方案。在这里,你从某个地方导入了 range 和 utils,这让我很难得到它:(
  • rangeutils 在答案中定义。向您展示这些部分是独立的并且不应该与您的组件放在同一个文件中是有意义的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-16
  • 2021-11-21
  • 2021-08-03
相关资源
最近更新 更多