You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Scala中使用Monocle(Lens)解析深度嵌套JSON提取目标键值映射

我之前看过使用Monocle解析深度嵌套JSON的示例,但现有示例都比较简单,我遇到了结构更复杂的JSON需要处理,结构如下:

{
  "products": {
    "HY3BZPP2B6K8MSJF" : {
      "sku" : "HY3BZPP2B6K8MSJF",
      "productFamily" : "Storage",
      "attributes" : {
          "servicecode" : "AmazonEC2",
          "location" : "US East (N. Virginia)",
          "locationType" : "AWS Region",
          "storageMedia" : "SSD-backed",
          "volumeType" : "General Purpose",
          "maxVolumeSize" : "16 TiB",
          "maxIopsvolume" : "16000",
          "maxIopsBurstPerformance" : "3000 for volumes <= 1 TiB",
          "maxThroughputvolume" : "250 MiB/s",
          "usagetype" : "EBS:VolumeUsage.gp2",
          "operation" : "",
          "servicename" : "Amazon Elastic Compute Cloud",
          "volumeApiName" : "gp2"
      }
    },
    ...
  },
  "terms" : {
    "OnDemand" : {
      "HY3BZPP2B6K8MSJF" : {
         "HY3BZPP2B6K8MSJF.JRTCKXETXF" : {
           "offerTermCode" : "JRTCKXETXF",
           "sku" : "HY3BZPP2B6K8MSJF",
           "effectiveDate" : "2021-10-01T00:00:00Z",
           "priceDimensions" : {
              "HY3BZPP2B6K8MSJF.JRTCKXETXF.6YS6EN2CT7" : {
                "rateCode" : "HY3BZPP2B6K8MSJF.JRTCKXETXF.6YS6EN2CT7",
                "description" : "$0.10 per GB-month of General Purpose SSD (gp2) provisioned storage - US East (Northern Virginia)",
                "beginRange" : "0",
                "endRange" : "Inf",
                "unit" : "GB-Mo",
                "pricePerUnit" : {
                    "USD" : "0.1000000000"
                },
                "appliesTo" : [ ]
            }
        },
        "termAttributes" : { }
      }
    },
  ...
 }

我目前可以通过如下代码完成基础解析:

val productData = productConnector.getProducts(100).map(p => Serializer.fromJson[ProductInfo](p))

但我不知道如何编写对应的Lens来提取生成键为volumeApiName、值为pricePerUnit的Map,比如(gp2 -> 0.1000000000)的格式。注意可以通过volumeApiName对应的sku作为关联字段,匹配terms下OnDemand节点的对应内容后逐层深入获取pricePerUnit的值。


实现代码

首先保证反序列化用的ProductInfo样例类层级和JSON结构对齐,参考样例类定义和提取逻辑如下:

import monocle.syntax.all._
import monocle.function.all._
import monocle.gen.all._

// 层级样例类,可根据你实际的序列化结构调整字段
case class PricePerUnit(USD: String)
case class PriceDimension(pricePerUnit: PricePerUnit)
case class OfferTerm(priceDimensions: Map[String, PriceDimension])
case class OnDemandSkuEntry(terms: Map[String, OfferTerm])
case class Terms(OnDemand: Map[String, OnDemandSkuEntry])
case class ProductAttributes(volumeApiName: String)
case class Product(sku: String, attributes: ProductAttributes)
case class ProductInfo(products: Map[String, Product], terms: Terms)

// 提取目标Map的逻辑
val volumePriceMap: Map[String, BigDecimal] = productData.flatMap { info =>
  info.products.values.flatMap { product =>
    // 用sku作为关联键,逐层查询对应价格
    val priceOpt = info.focus(_.terms.OnDemand)
      .index(product.sku) // 匹配OnDemand下对应sku的条目
      .focus(_.terms.values.headOption) // 每个sku下仅对应1个offer term,取第一个
      .some
      .focus(_.priceDimensions.values.headOption) // 每个offer term下仅对应1个计费维度,取第一个
      .some
      .focus(_.pricePerUnit.USD)
      .getOption
      .map(BigDecimal(_))
    
    priceOpt.map(price => product.attributes.volumeApiName -> price)
  }
}.toMap

要点说明

  • 动态Map键的访问使用Monocle提供的index方法,自动处理键不存在的情况
  • 可选值穿透使用some语法,任意一层节点不存在时,getOption会直接返回None,无需手动做空判断
  • 最终返回的volumePriceMap就是你需要的volumeApiName对应USD价格的映射,价格已经转成BigDecimal类型方便后续计算

内容的提问来源于stack exchange,提问作者happygopher

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 15:06:00