使用spray-json在SCALA中解析复杂的JSON

4
我正在尝试将一个JSON字符串解析为Scala中的case类(这样我就可以进行数据处理等过滤操作)。 经过一些研究,我选择使用spray-json,因为该链接提供了几个示例。不幸的是,该链接没有展示如何解析具有数组的嵌套字段的JSON。 我在Scala笔记本上测试了下面的代码,它可以工作。
// Dependencies
io.spray spray-json_2.10 1.3.2
import spray.json._
import DefaultJsonProtocol._ // if you don't supply your own Protocol (see below)

// simple source
val source = """{
  "EventId": "29ca61f3-b8b6-41e7-8236-802fa232e7cf",
  "Timestamp": "2016-03-09T20:14:07.5535193Z",
  "StartTime": "2016-03-09T02:51:04.397",
  "EndTime": "2016-03-09T02:51:04.397",
  "ActiveStates": "{\"No Motion\":1,\"Motion Detected\":1,\"Face Detected\":1}",
  "Created": "2016-03-09T02:51:04.397",
  "Modified": "2016-03-09T02:51:04.397"
}"""

// simple case class
case class claX(  EventId: String,
  Timestamp: String,
  StartTime: String,
  EndTime: String,
  ActiveStates: String,
  Created: String,
  Modified: String)


object MyJsonProtocol extends DefaultJsonProtocol {
  implicit val claXFormat = jsonFormat7(claX)
}
import MyJsonProtocol._
import spray.json._


val json = source.parseJson // parse string to json
val cx0 = json.convertTo[claX] // convert to class claX

我的问题是当JSON字符串中的数据有一个包含嵌套“Product”类的嵌套数组时。以下是示例JSON:

{
      "EventId": "29ca61f3-b8b6-41e7-8236-802fa232e7cf",
      "Timestamp": "2016-03-09T20:14:07.5535193Z",
      "StartTime": "2016-03-09T02:51:04.397",
      "EndTime": "2016-03-09T02:51:04.397",
      "ActiveStates": "{\"No Motion\":1,\"Motion Detected\":1,\"Face Detected\":1}",
      "Created": "2016-03-09T02:51:04.397",
      "Modified": "2016-03-09T02:51:04.397",
      "Data": {
        "AgeRange": {
          "Name": "30 - 35"
        },
        "Company": {
          "Id": "f3ad1744-0ead-458a-9416-852c43ccde24"
        },
        "CompanyType": {
          "Name": "Retailer"
        },
        "ConnectorType": {
          "Name": "Camera Capturing"
        },
        "Content": {
          "Ids": [
            "0c0f0a9a-fece-4b3e-abb4-0f508d357220"
          ]
        },
        "Customer": {
          "LoyaltyId": 0
        },
        "DeviceRegistries": [
          {
            "Id": "f19f5daa-e9b9-43d0-91a7-51da4fdd0e31",
            "DeviceName": "Company 3 Cooler",
            "DeviceType": "CCU"
          }
        ],
        "Emotion": {
          "Name": "Happy"
        },
        "Gender": {
          "Name": "Male"
        },
        "Products": [
          {
            "Name": "Molson Canadian",
            "ProductCategory": "Beverage",
            "InventoryTrackingNumberType": "SKU",
            "InventoryTrackingNumber": "438654935776",
            "ProductPrice": {
              "RetailPrice": 2.1,
              "RetailPriceSymbol": "?",
              "PromotionPrice": 1.8,
              "PromotionPriceSymbol": "?"
            }
          },
          {
            "Name": "Coors Original",
            "ProductCategory": "Beverage",
            "InventoryTrackingNumberType": "SKU",
            "InventoryTrackingNumber": "438654935775",
            "ProductPrice": {
              "RetailPrice": 1.1,
              "RetailPriceSymbol": "?",
              "PromotionPrice": 0.8,
              "PromotionPriceSymbol": "?"
            }
          },
          {
            "Name": "Coors Light",
            "ProductCategory": "Beverage",
            "InventoryTrackingNumberType": "SKU",
            "InventoryTrackingNumber": "438654935778",
            "ProductPrice": {
              "RetailPrice": 6.1,
              "RetailPriceSymbol": "?",
              "PromotionPrice": 5.8,
              "PromotionPriceSymbol": "?"
            }
          },
          {
            "Name": "Blue Moon",
            "ProductCategory": "Beverage",
            "InventoryTrackingNumberType": "SKU",
            "InventoryTrackingNumber": "438654935777",
            "ProductPrice": {
              "RetailPrice": 4.1,
              "RetailPriceSymbol": "?",
              "PromotionPrice": 3.8,
              "PromotionPriceSymbol": "?"
            }
          }
        ],
        "Race": {
          "Name": "Latin"
        },
        "Region": {
          "Name": "Region 01"
        },
        "SensorRegistry": {
          "Name": "Company 3 Camera 01"
        },
        "SensorType": {
          "Name": "Proximity"
        },
        "SfuRegistries": {
          "Ids": [
            "7effea8c-56dd-4905-bbc3-2158d14cd7cc",
            "24a7253d-174a-44f0-8145-483cc0f45adb",
            "bc970c8e-7e41-4889-859b-55c6a3f8ba5d",
            "46e599f5-8082-499f-b5d0-9d611409a652"
          ]
        },
        "Shelves": {
          "Ids": [
            "ea442504-7d64-4c01-bdde-1eb46e53b81c",
            "d6fe9c78-e21b-4a57-b620-99a7d94d46f9"
          ]
        },
        "State": {
          "Name": "Face Detected"
        },
        "StockLevel": {
          "OnHand": 0
        },
        "Store": {
          "Id": "268c852d-86b8-4b7c-b865-2f29a3e2307e"
        },
        "Unit": {
          "Id": "52c58781-b2bf-46ea-81ad-b9d9fbacb471"
        },
        "UnitType": {
          "Name": "5-Shelf Cooler"
        }
      },
      "id": "54bfd971-0fec-4e0e-87cc-851a697705e9"
    }

我已经创建了两个case类来管理“产品”和“价格”

case class ProductPrice(RetailPrice: Double,
          RetailPriceSymbol: Double,
          PromotionPrice: Double,
          PromotionPriceSymbol: Double)

case class Product(Name: String,
        ProductCategory: String,
        InventoryTrackingNumberType: String,
        InventoryTrackingNumber: String,
        ProductPrice: ProductPrice)

我不知道的是,如何将JSON中的Data节点正确解析为claXBig(从JSON字符串中正确解析出所有内容)。 这就是我遇到麻烦的地方:

case class claX2(  EventId: String,
  Timestamp: String,
  StartTime: String,
  EndTime: String,
  ActiveStates: String,
  Created: String,
  Modified: String,
  Data: Map[String, Any]) // <- how do I parse this and the nested products

object MyJsonProtocol2 extends DefaultJsonProtocol {
  implicit val claXFormat2 = jsonFormat8(claX2)
}

我也尝试使用这里列出的代码加载更大的JSON(这些“事件”的集合)。
因此,我添加了一个新的case类来处理'event'或claX2的数组。
case class claX2Collection(clax2s: Array[claX2])
    extends IndexedSeq[claX2] {
  def apply(index: Int) = clax2s(index) //<- not sure what this mean 
  def length = clax2s.length // or whether index is doing anything
}

我认为claX2Collection是正确的,因为它可以通过编译。但是下面的代码明显是错误的,但需要用来从JSON数组加载事件集合。
implicit object claX2JsonFormat extends RootJsonFormat[claX2]{
def write(f: claX2) = {
val buf = scala.collection.mutable.ArrayBuffer(
"events" -> JsString("claX2"), // <- error
"Timestamp" -> JsObject(f.Timestamp), // error
"StartTime" -> JsObject(f.StartTime), // error
"EndTime" -> JsObject(f.EndTime), // error
"ActiveStates" -> JsObject(f.ActiveStates), // error
"Created" -> JsObject(f.Created), // errors
"Modified" -> JsObject(f.Modified), // errors
"Data" -> JsObject(f.Data) // errors
)
}
def read(value:JsValue) = {
val jso = value.asJsObject
// not sure what to do here but
// assuming I have to pick out
val EventId = jso.fields.get("EventId")
Timestamp = jso.fields.get("Timestamp")
StartTime = jso.fields.get("StartTime")
EndTime = jso.fields.get("EndTime")
ActiveStates = jso.fields.get("ActiveStates")
Created = jso.fields.get("Created")
Modified = jso.fields.get("Modified")
Data = jso.fields.get("Data")
claX2(EventId,Timestamp,StartTime,EndTime,ActiveStates,Created,
Modified,Data)
}
}

当这个问题被解决后,它应该能够读取这个JSON:
    {
  "type": "EventCollection",

"events": [
{
  "EventId": "29ca61f3-b8b6-41e7-8236-802fa232e7cf",
  "Timestamp": "2016-03-09T20:14:07.5535193Z",
  "StartTime": "2016-03-09T02:51:04.397",
  "EndTime": "2016-03-09T02:51:04.397",
  "ActiveStates": "{\"No Motion\":1,\"Motion Detected\":1,\"Face Detected\":1}",
  "Created": "2016-03-09T02:51:04.397",
  "Modified": "2016-03-09T02:51:04.397",
  "Data": {
    "AgeRange": {
      "Name": "30 - 35"
    },
    "Company": {
      "Id": "f3ad1744-0ead-458a-9416-852c43ccde24"
    },
    "CompanyType": {
      "Name": "Retailer"
    },
    "ConnectorType": {
      "Name": "Camera Capturing"
    },
    "Content": {
      "Ids": [
        "0c0f0a9a-fece-4b3e-abb4-0f508d357220"
      ]
    },
    "Customer": {
      "LoyaltyId": 0
    },
    "DeviceRegistries": [
      {
        "Id": "f19f5daa-e9b9-43d0-91a7-51da4fdd0e31",
        "DeviceName": "Company 3 Cooler",
        "DeviceType": "CCU"
      }
    ],
    "Emotion": {
      "Name": "Happy"
    },
    "Gender": {
      "Name": "Male"
    },
    "Products": [
      {
        "Name": "Molson Canadian",
        "ProductCategory": "Beverage",
        "InventoryTrackingNumberType": "SKU",
        "InventoryTrackingNumber": "438654935776",
        "ProductPrice": {
          "RetailPrice": 2.1,
          "RetailPriceSymbol": "?",
          "PromotionPrice": 1.8,
          "PromotionPriceSymbol": "?"
        }
      },
      {
        "Name": "Coors Original",
        "ProductCategory": "Beverage",
        "InventoryTrackingNumberType": "SKU",
        "InventoryTrackingNumber": "438654935775",
        "ProductPrice": {
          "RetailPrice": 1.1,
          "RetailPriceSymbol": "?",
          "PromotionPrice": 0.8,
          "PromotionPriceSymbol": "?"
        }
      },
      {
        "Name": "Coors Light",
        "ProductCategory": "Beverage",
        "InventoryTrackingNumberType": "SKU",
        "InventoryTrackingNumber": "438654935778",
        "ProductPrice": {
          "RetailPrice": 6.1,
          "RetailPriceSymbol": "?",
          "PromotionPrice": 5.8,
          "PromotionPriceSymbol": "?"
        }
      },
      {
        "Name": "Blue Moon",
        "ProductCategory": "Beverage",
        "InventoryTrackingNumberType": "SKU",
        "InventoryTrackingNumber": "438654935777",
        "ProductPrice": {
          "RetailPrice": 4.1,
          "RetailPriceSymbol": "?",
          "PromotionPrice": 3.8,
          "PromotionPriceSymbol": "?"
        }
      }
    ],
    "Race": {
      "Name": "Latin"
    },
    "Region": {
      "Name": "Region 01"
    },
    "SensorRegistry": {
      "Name": "Company 3 Camera 01"
    },
    "SensorType": {
      "Name": "Proximity"
    },
    "SfuRegistries": {
      "Ids": [
        "7effea8c-56dd-4905-bbc3-2158d14cd7cc",
        "24a7253d-174a-44f0-8145-483cc0f45adb",
        "bc970c8e-7e41-4889-859b-55c6a3f8ba5d",
        "46e599f5-8082-499f-b5d0-9d611409a652"
      ]
    },
    "Shelves": {
      "Ids": [
        "ea442504-7d64-4c01-bdde-1eb46e53b81c",
        "d6fe9c78-e21b-4a57-b620-99a7d94d46f9"
      ]
    },
    "State": {
      "Name": "Face Detected"
    },
    "StockLevel": {
      "OnHand": 0
    },
    "Store": {
      "Id": "268c852d-86b8-4b7c-b865-2f29a3e2307e"
    },
    "Unit": {
      "Id": "52c58781-b2bf-46ea-81ad-b9d9fbacb471"
    },
    "UnitType": {
      "Name": "5-Shelf Cooler"
    }
  },
  "id": "54bfd971-0fec-4e0e-87cc-851a697705e9"
},
{
  "EventId": "29ca61f3-b8b6-41e7-8236-802fa232e7cf",
  "Timestamp": "2016-03-09T20:14:07.5535193Z",
  "StartTime": "2016-03-09T02:51:04.397",
  "EndTime": "2016-03-09T02:51:04.397",
  "ActiveStates": "{\"No Motion\":1,\"Motion Detected\":1,\"Face Detected\":1}",
  "Created": "2016-03-09T02:51:04.397",
  "Modified": "2016-03-09T02:51:04.397",
  "Data": {
    "AgeRange": {
      "Name": "30 - 35"
    },
    "Company": {
      "Id": "f3ad1744-0ead-458a-9416-852c43ccde24"
    },
    "CompanyType": {
      "Name": "Retailer"
    },
    "ConnectorType": {
      "Name": "Camera Capturing"
    },
    "Content": {
      "Ids": [
        "0c0f0a9a-fece-4b3e-abb4-0f508d357220"
      ]
    },
    "Customer": {
      "LoyaltyId": 0
    },
    "DeviceRegistries": [
      {
        "Id": "f19f5daa-e9b9-43d0-91a7-51da4fdd0e31",
        "DeviceName": "Company 3 Cooler",
        "DeviceType": "CCU"
      }
    ],
    "Emotion": {
      "Name": "Happy"
    },
    "Gender": {
      "Name": "Male"
    },
    "Products": [
      {
        "Name": "Molson Canadian",
        "ProductCategory": "Beverage",
        "InventoryTrackingNumberType": "SKU",
        "InventoryTrackingNumber": "438654935776",
        "ProductPrice": {
          "RetailPrice": 2.1,
          "RetailPriceSymbol": "?",
          "PromotionPrice": 1.8,
          "PromotionPriceSymbol": "?"
        }
      },
      {
        "Name": "Coors Original",
        "ProductCategory": "Beverage",
        "InventoryTrackingNumberType": "SKU",
        "InventoryTrackingNumber": "438654935775",
        "ProductPrice": {
          "RetailPrice": 1.1,
          "RetailPriceSymbol": "?",
          "PromotionPrice": 0.8,
          "PromotionPriceSymbol": "?"
        }
      },
      {
        "Name": "Coors Light",
        "ProductCategory": "Beverage",
        "InventoryTrackingNumberType": "SKU",
        "InventoryTrackingNumber": "438654935778",
        "ProductPrice": {
          "RetailPrice": 6.1,
          "RetailPriceSymbol": "?",
          "PromotionPrice": 5.8,
          "PromotionPriceSymbol": "?"
        }
      },
      {
        "Name": "Blue Moon",
        "ProductCategory": "Beverage",
        "InventoryTrackingNumberType": "SKU",
        "InventoryTrackingNumber": "438654935777",
        "ProductPrice": {
          "RetailPrice": 4.1,
          "RetailPriceSymbol": "?",
          "PromotionPrice": 3.8,
          "PromotionPriceSymbol": "?"
        }
      }
    ],
    "Race": {
      "Name": "Latin"
    },
    "Region": {
      "Name": "Region 01"
    },
    "SensorRegistry": {
      "Name": "Company 3 Camera 01"
    },
    "SensorType": {
      "Name": "Proximity"
    },
    "SfuRegistries": {
      "Ids": [
        "7effea8c-56dd-4905-bbc3-2158d14cd7cc",
        "24a7253d-174a-44f0-8145-483cc0f45adb",
        "bc970c8e-7e41-4889-859b-55c6a3f8ba5d",
        "46e599f5-8082-499f-b5d0-9d611409a652"
      ]
    },
    "Shelves": {
      "Ids": [
        "ea442504-7d64-4c01-bdde-1eb46e53b81c",
        "d6fe9c78-e21b-4a57-b620-99a7d94d46f9"
      ]
    },
    "State": {
      "Name": "Face Detected"
    },
    "StockLevel": {
      "OnHand": 0
    },
    "Store": {
      "Id": "268c852d-86b8-4b7c-b865-2f29a3e2307e"
    },
    "Unit": {
      "Id": "52c58781-b2bf-46ea-81ad-b9d9fbacb471"
    },
    "UnitType": {
      "Name": "5-Shelf Cooler"
    }
  },
  "id": "54bfd971-0fec-4e0e-87cc-851a697705e9"
}
]
}

从来没有使用过spray-json,通常我会选择play-json。如果你愿意尝试一下,可以看看http://pedrorijo.com/blog/scala-json/和http://pedrorijo.com/blog/scala-json-part2/。 - pedrorijo91
最快的解决方案 - 将Map[String, Any]更改为JsObject,然后为其创建下一个case class。 - Andrzej Jozwik
@pedrorijo91 谢谢你提供的两个链接,我会阅读它们并将其添加到工具箱中。 - Rocket Surgeon
2个回答

3
这是完整的解决方案。解决方案和数据文件的git仓库在这里
    import spray.json._
import DefaultJsonProtocol._

object parseJson {
    def main(args: Array[String]){

    // case classes for all the nested information in the 
    case class Ids(Ids: Seq[String])
    case class Id(Id: String)
    case class Name(Name: String)
    case class OnHand(OnHand: Int)
    case class LoyaltyId(LoyaltyId: Int)

    case class ProductPrice(RetailPrice: Double,
                            RetailPriceSymbol: String,
                            PromotionPrice: Double,
                            PromotionPriceSymbol: String)

    case class Product(Name: String,
                       ProductCategory: String,
                       InventoryTrackingNumberType: String,
                       InventoryTrackingNumber: String,
                       ProductPrice: ProductPrice)

    case class Data(Content: Ids,
                    SfuRegistries: Ids,
                    AgeRange: Name,
                    Company: Id,
                    SensorType: Name,
                    StockLevel: OnHand,
                    Region: Name,
                    UnitType: Name,
                    Emotion: Name,
                    Shelves: Ids,
                    Customer: LoyaltyId,
                    DeviceRegistries: Seq[Map[String, String]],
                    ConnectorType: Name,
                    CompanyType: Name,
                    State: Name,
                    Gender: Name,
                    SensorRegistry: Name,
                    Race: Name,
                    Store: Id,
                    Products: Seq[Product])

    case class Element(EventId: String,
                     Timestamp: String,
                     StartTime: String,
                     EndTime: String,
                     ActiveStates: String,
                     Created: String,
                     Modified: String,
                     Data: Data)

 // This is the code that is blowing up
 case class RootCollection(items: Array[Element]) extends IndexedSeq[Element]{
    def apply(index: Int) = items(index)
    def length = items.length
}

object MyJsonProtocol extends DefaultJsonProtocol {
  implicit val nameFormat = jsonFormat1(Name)
  implicit val productPriceFormat = jsonFormat4(ProductPrice)
  implicit val productFormat = jsonFormat5(Product)
  implicit val loyaltyIdFormat = jsonFormat1(LoyaltyId)
  implicit val onHandFormat = jsonFormat1(OnHand)
  implicit val idFormat = jsonFormat1(Id)
  implicit val idsFormat = jsonFormat1(Ids)
  implicit val dateFormat = jsonFormat20(Data)
  implicit val ElementFormat = jsonFormat8(Element)  
  implicit object RootCollectionFormat extends RootJsonFormat[RootCollection] {
    def read(value: JsValue) = RootCollection(value.convertTo[Array[Element]])
    def write(f: RootCollection) = JsArray(f.toJson)
  }
 }

 import MyJsonProtocol._

        println("Running Parse JSON")
        val input = scala.io.Source.fromFile("sample2.json")("UTF-8").mkString.parseJson
        //println("JSON string read:")
        //println(input)

        val jsonCollection = input.convertTo[RootCollection]

        // print some items
        jsonCollection.map(y => y.Data.Products.map(x => println(x)))
        println(jsonCollection.length)

    }
}

0

更少一些像 :-)

  case class claX2(
    EventId: String,
    Timestamp: String,
    StartTime: String,
    EndTime: String,
    ActiveStates: String,
    Created: String,
    Modified: String,
    Data: Data)

  case class Data(Content: Ids,
    SfuRegistries: Ids,
    AgeRange: Name,
    Company: Id,
    SensorType: Name,
    StockLevel: OnHand,
    Region: Name,
    UnitType: Name,
    Emotion: Name,
    Shelves: Ids,
    Customer: LoyaltyId,
    DeviceRegistries: Seq[Map[String, String]],
    ConnectorType: Name,
    CompanyType: Name,
    State: Name,
    Gender: Name,
    SensorRegistry: Name,
    Race: Name,
    Store: Id,
    Products: Seq[Product])

  case class Ids(Ids: Seq[String])

  case class Id(Id: String)

  case class Name(Name: String)

  case class OnHand(OnHand: Int)

  case class LoyaltyId(LoyaltyId: Int)

  case class ProductPrice(RetailPrice: Double, 
   RetailPriceSymbol: String,
    PromotionPrice: Double,
    PromotionPriceSymbol: String)

  case class Product(Name: String, 
                    ProductCategory: String, 
                    InventoryTrackingNumberType: String,
                    InventoryTrackingNumber: String, 
                    ProductPrice: ProductPrice)

  object MyJsonProtocol2 extends DefaultJsonProtocol {
    implicit val nameFormat = jsonFormat1(Name)
    implicit val productPriceFormat = jsonFormat4(ProductPrice)
    implicit val productFormat = jsonFormat5(Product)
    implicit val loyaltyIdFormat = jsonFormat1(LoyaltyId)
    implicit val onHandFormat = jsonFormat1(OnHand)
    implicit val idFormat = jsonFormat1(Id)
    implicit val idsFormat = jsonFormat1(Ids)
    implicit val dateFormat = jsonFormat20(Data)
    implicit val claXFormat2 = jsonFormat8(claX2)
  }

@AndrzeyJoswik 这个解决方案对样例有效(谢谢),我需要让它在即将拥有的大型JSON文件中的claX2数组中起作用。 - Rocket Surgeon
如果您使用通用映射,可以仅使用JsObject而不是Map[String, Any]或Map[String, JsObject],然后解析下一部分。 - Andrzej Jozwik
@AndrzeyJoswick 我一直在尝试通过调整这里的代码框架 链接 来适应加载JSON事件集合的解决方案,以便我可以对每个事件元素使用.filter .map .foreach等方法。但是无法解析。 - Rocket Surgeon
添加了我正在尝试适应的代码,以读取JSON中的元素数组。 - Rocket Surgeon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接