将circe中json对象的所有键从“下划线”转换为“驼峰式”

Posted

技术标签:

【中文标题】将circe中json对象的所有键从“下划线”转换为“驼峰式”【英文标题】:Transform all keys from `underscore` to `camel case` of json objects in circe 【发布时间】:2016-10-01 17:16:21 【问题描述】:

原产地


  "first_name" : "foo",
  "last_name" : "bar",
  "parent" : 
    "first_name" : "baz",
    "last_name" : "bazz",
  

预期

 
      "firstName" : "foo",
      "lastName" : "bar",
      "parent" : 
        "firstName" : "baz",
        "lastName" : "bazz",
      
    

如何转换 json 对象的所有键?

【问题讨论】:

@Thilo 是的,它的相机盒 【参考方案1】:

我会这样写。它不像我想要的那样简洁,但并不可怕:

import cats.free.Trampoline
import cats.std.list._
import cats.syntax.traverse._
import io.circe. Json, JsonObject 

/**
 * Helper method that transforms a single layer.
 */
def transformObjectKeys(obj: JsonObject, f: String => String): JsonObject =
  JsonObject.fromIterable(
    obj.toList.map 
      case (k, v) => f(k) -> v
    
  )

def transformKeys(json: Json, f: String => String): Trampoline[Json] =
  json.arrayOrObject(
    Trampoline.done(json),
    _.traverse(j => Trampoline.suspend(transformKeys(j, f))).map(Json.fromValues),
    transformObjectKeys(_, f).traverse(obj => Trampoline.suspend(transformKeys(obj, f))).map(Json.fromJsonObject)
  )

然后:

import io.circe.literal._

val doc = json"""

  "first_name" : "foo",
  "last_name" : "bar",
  "parent" : 
    "first_name" : "baz",
    "last_name" : "bazz"
  

"""

def sc2cc(in: String) = "_([a-z\\d])".r.replaceAllIn(in, _.group(1).toUpperCase)

最后:

scala> import cats.std.function._
import cats.std.function._

scala> transformKeys(doc, sc2cc).run
res0: io.circe.Json =

  "firstName" : "foo",
  "lastName" : "bar",
  "parent" : 
    "firstName" : "baz",
    "lastName" : "bazz"
  

我们可能应该有某种方法更方便地递归应用像这样的Json => F[Json] 转换。

【讨论】:

是否添加了更简洁的方法?看起来这将是一个常见的用例。【参考方案2】:

根据您的完整用例,对于最新的 Circe,您可能更喜欢根据这些参考资料利用现有的解码器/编码器在骆驼/蛇之间进行转换:

https://dzone.com/articles/5-useful-circe-feature-you-may-have-overlooked https://github.com/circe/circe/issues/663

例如,在我的特定用例中,这是有道理的,因为我正在执行其他操作,这些操作受益于首先反序列化为案例类的类型安全性。因此,如果您愿意将 JSON 解码为案例类,然后将其编码回 JSON,那么您所需要的只是(反)序列化代码来扩展配置它的特征,例如:

import io.circe.derivation._
import io.circe.Decoder, Encoder, ObjectEncoder, derivation
import io.circe.generic.auto._
import io.circe.parser.decode
import io.circe.syntax._

trait JsonSnakeParsing 
  implicit val myCustomDecoder: Decoder[MyCaseClass] = deriveDecoder[MyCaseClass](io.circe.derivation.renaming.snakeCase)
  // only needed if you want to serialize back to snake case json:
  // implicit val myCustomEncoder: ObjectEncoder[MyCaseClass] = deriveEncoder[MyCaseClass](io.circe.derivation.renaming.snakeCase)

例如,我在实际解析或输出 JSON 时对其进行扩展:

trait Parsing extends JsonSnakeParsing 

  val result: MyCaseClass = decode[MyCaseClass](scala.io.Source.fromResource("my.json").mkString) match 
    case Left(jsonError) => throw new Exception(jsonError)
    case Right(source) => source
  

  val theJson = result.asJson

对于此示例,您的案例类可能如下所示:

case class MyCaseClass(firstName: String, lastName: String, parent: MyCaseClass)

这是我在此示例中的完整 circe 依赖项列表:

val circeVersion = "0.10.0-M1"

"io.circe" %% "circe-generic" % circeVersion,
"io.circe" %% "circe-parser" % circeVersion,
"io.circe" %% "circe-generic-extras" % circeVersion,
"io.circe" %% "circe-derivation" % "0.9.0-M5",

【讨论】:

这现在应该是一个公认的答案。使用内置功能而不是自己实现【参考方案3】:
def transformKeys(json: Json, f: String => String): TailRec[Json] = 
      if(json.isObject) 
        val obj = json.asObject.get
        val fields = obj.toList.foldLeft(done(List.empty[(String, Json)]))  (r, kv) =>
          val (k, v) = kv
          for 
            fs <- r
            fv <- tailcall(transformKeys(v, f))
           yield fs :+ (f(k) -> fv)
        
        fields.map(fs => Json.obj(fs: _*))
       else if(json.isArray) 
        val arr = json.asArray.get
        val vsRec = arr.foldLeft(done(List.empty[Json]))  (vs, v) =>
          for 
            s <- vs
            e <- tailcall(transformKeys(v, f))
           yield s :+ e
        
        vsRec.map(vs => Json.arr(vs: _*))
       else 
        done(json)
      
    

目前我是这样改造的,但是比较复杂,希望有一个简单的方法。

【讨论】:

【参考方案4】:

我采用了@Travis 的答案并对其进行了一些现代化改造,我采用了他的代码,但我遇到了一些错误和警告,因此带有 Cats 1.0.0-MF 的 Scala 2.12 的更新版本:

import io.circe.literal._
import cats.free.Trampoline, cats.instances.list._, cats.instances.function._, cats.syntax.traverse._, cats.instances.option._

def transformKeys(json: Json, f: String => String): Trampoline[Json] = 
  def transformObjectKeys(obj: JsonObject, f: String => String): JsonObject =
    JsonObject.fromIterable(
      obj.toList.map 
        case (k, v) => f(k) -> v
      
    )
  json.arrayOrObject(
    Trampoline.done(json),
    _.toList.traverse(j => Trampoline.defer(transformKeys(j, f))).map(Json.fromValues(_)),
    transformObjectKeys(_, f).traverse(obj => Trampoline.defer(transformKeys(obj, f))).map(Json.fromJsonObject)
  )


def sc2cc(in: String) = "_([a-z\\d])".r.replaceAllIn(in, _.group(1).toUpperCase)

def camelizeKeys(json: io.circe.Json) = transformKeys(json, sc2cc).run

【讨论】:

以上是关于将circe中json对象的所有键从“下划线”转换为“驼峰式”的主要内容,如果未能解决你的问题,请参考以下文章

在Ruby中将嵌套哈希键从CamelCase转换为snake_case

无法使用Circe JSON解析器创建遍历JSON字符串的对象

如何使用 circe 将密封特征案例对象转换为字符串

Scala使用circe将None编码为NaN json值

使用 circe 将 Scala None 编码为 JSON 值

如何根据 apache hive 中的键从 json 列表中提取 json 对象