如何修改 JsonObject 的值

How to modify values of JsonObject

我想向 jsonObject 添加一个新字段,这个新字段的名称将基于另一个字段的值。需要说明的是,这是我想要实现的示例。

{
  "values": [
    {
      "id": "1",
      "properties": [
        {
          "stat": "memory",
          "data": 8
        },
        {
          "stat": "cpu",
          "data": 4
        }
      ]
    },
    {
      "id": "2",
      "properties": [
        {
          "stat": "status",
          "data": "OK"
        },
        {
          "stat": "cpu",
          "data": 4
        }
      ]
    }
  ]
}

我想为每个 json 对象添加一个新字段,将字段“stat”的值作为名称。

{
  "values": [
    {
      "id": "1",
      "properties": [
        {
          "stat": "memory",
          "data": 8,
          "memory": 8
        },
        {
          "stat": "cpu",
          "data": 4,
          "cpu": 4
        }
      ]
    },
    {
      "id": "2",
      "properties": [
        {
          "stat": "status",
          "data": 0,
          "status": 0
        },
        {
          "stat": "cpu",
          "data": 4,
          "cpu": 4
        }
      ]
    }
  ]
}

我尝试使用 JsonPath 库执行以下操作,但对我来说这是一个丑陋的解决方案,因为我将解析 json 三次并进行一些手动替换。

val configuration = Configuration.builder().options(Option.DEFAULT_PATH_LEAF_TO_NULL, Option.ALWAYS_RETURN_LIST).build()
val jsonContext5 = JsonPath.using(configuration).parse(jsonStr)
val listData = jsonContext.read("$['values'][*]['properties'][*]['data']").toString
      .replace("[", "").replace("]", "").split(",").toList
val listStat = jsonContext.read("$['values'][*]['properties'][*]['stat']").toString
      .replace("[", "").replace("]", "")
      .replace("\"", "").split(",").toList
// Replacing values of "stat" by values of "data"
jsonContext5.map("$['values'][*]['properties'][*]['stat']", new MapFunction() {
      var count = - 1
      override def map(currentValue: Any, configuration: Configuration): AnyRef = {
        count += 1
        listData(count)
      }
    })
// replace field stat by its value
for( count <- 0 to listStat.size - 1){
     val path = s"['values'][*]['properties'][$count]"
     jsonContext5.renameKey(path, "stat", s"${listStat(count)}")
}
    

这是得到的结果

{
  "values": [
    {
      "id": "1",
      "properties": [
        {
          "data": 8,
          "memory": "8"
        },
        {
          "data": 4,
          "cpu": "4"
        }
      ]
    },
    {
      "id": "2",
      "properties": [
        {
          "data": 0,
          "memory": "0"
        },
        {
          "data": 4,
          "cpu": "4"
        }
      ]
    }
  ]
}

有没有更好的方法可以达到这个效果?我试着用 gson 来做,但它不是很好的处理路径。

这是一种使用 Gson 的方法,但我会丢失有关其他列的信息,因为我正在创建另一个 json。

val jsonArray = jsonObject.get("properties").getAsJsonArray
val iter = jsonArray.iterator()
val agreedJson = new JsonArray()
while(iter.hasNext) {
    val json = iter.next().getAsJsonObject
    agreedJson.add(replaceCols(json))
}
def replaceCols(json: JsonObject) = {
    val fieldName = "stat"
    if(json.has(fieldName)) {
      val columnName = json.get(fieldName).getAsString
      val value: String = if (json.has("data")) json.get("data").getAsString else ""
      json.addProperty(columnName, value)
    }
    json
}

使用 Gson,您应该做的是创建一个基础 class 来表示您的初始 JSON 对象。然后,扩展 class 并添加您要添加​​的附加属性,例如“stat”。然后,将 JSON 个对象一个接一个或一起加载到内存中,然后对每个对象进行必要的更改以包含您的更改。然后,将这些更改映射到新的 class(如果您在之前的步骤中没有这样做),并将它们序列化到文件或其他存储中。

这样的怎么样?

private static void statDup(final JSONObject o) {
    if (o.containsKey("properties")) {
        final JSONArray a = (JSONArray) o.get("properties");
        for (final Object e : a) {
            final JSONObject p = (JSONObject) e;
            p.put(p.get("stat"), p.get("data"));
        }
    } else {
        for (final Object key : o.keySet()) {
            final Object value = o.get(key);
            if (value instanceof JSONArray) {
                for (final Object e : (JSONArray) value) {
                    statDup((JSONObject) e);
                }
            }
        }
    }
}

Gene McCulley 之前的回答给出了 Java 和 class net.minidev.json 的解决方案。这个答案使用 class Gson 并用 Scala 编写。

def statDup(o: JsonObject): JsonObject = {
    if (o.has("properties")) {
      val a = o.get("properties").getAsJsonArray
      a.foreach { e =>
        val p = e.getAsJsonObject
        p.add(p.get("stat").getAsString, p.get("data"))
      }
    } else {
      o.keySet.foreach { key =>
        o.get(key) match {
          case jsonArr: JsonArray =>
            jsonArr.foreach { e =>
              statDup(e.getAsJsonObject)
            }
        }
      }
    }
    o
  }

这是类型安全的,纯 FP circe 实现 circe-optics:

object CirceOptics extends App {
  import cats.Applicative
  import cats.implicits._
  import io.circe.{Error => _, _}
  import io.circe.syntax._
  import io.circe.parser._
  import io.circe.optics.JsonPath._

  val jsonStr: String = ???

  def getStat(json: Json): Either[Error, String] =
    root.stat.string.getOption(json)
      .toRight(new Error(s"Missing stat of string type in $json"))

  def getData(json: Json): Either[Error, Json] =
    root.data.json.getOption(json)
      .toRight(new Error(s"Missing data of json type in $json"))
  
  def setField(json: Json, key: String, value: Json) =
    root.at(key).setOption(Some(value))(json)
      .toRight(new Error(s"Unable to set $key -> $value to $json"))
      
  def modifyAllPropertiesOfAllValuesWith[F[_]: Applicative](f: Json => F[Json])(json: Json): F[Json] =
    root.values.each.properties.each.json.modifyF(f)(json)

  val res = for {
    json          <-  parse(jsonStr)
    modifiedJson  <-  modifyAllPropertiesOfAllValuesWith { j =>
      for {
        stat  <-  getStat(j)
        data  <-  getData(j)
        prop  <-  setField(j, stat, data)
      } yield prop
    } (json)
  } yield modifiedJson

  println(res)
}

你的任务是在JSON文件中的每个属性下的每条记录中添加一个新的字段,使当前的统计值成为字段名,数据值成为新的字段值。如果您尝试在 Java.

中执行,代码将相当长

建议您使用 SPL,一个 open-source Java 包来完成它。编码会很容易,你只需要一行:

A
1 =json(json(file("data.json").read()).values.
run(properties=properties.(([["stat","data"]|stat]|[~.array()|data]).record())))

SPL 提供 JDBC 驱动程序供 Java 调用。只需将上述 SPL 脚本存储为 addfield.splx 并在调用存储过程时在 Java 应用程序中调用它:

…
Class.forName("com.esproc.jdbc.InternalDriver");
con= DriverManager.getConnection("jdbc:esproc:local://");
st = con.prepareCall("call addfield()");
st.execute();
…