Scala解析YAML文件过滤零值子类别时类型转换报错求助
BigInt cannot be cast to Double Error When Filtering circe JSON Values Hey there! Let's break down why you're hitting that cast error and fix it properly with idiomatic Scala + circe code.
The Root Cause
When circe parses your YAML, it infers numeric types automatically:
- Integer values like
0get parsed asBigInt - Decimal values like
1.5get parsed asBigDecimal
Your original code tries to force-cast everything to Double with asInstanceOf[Map[String,Double]], which fails because BigInt can't be directly cast to Double. Instead, we need to handle numeric types safely using circe's built-in APIs.
Step-by-Step Solution
First, let's clean up the parsing flow (no need to convert JSON to a string and re-parse it—we can work directly with the parsed Json value):
import io.circe._ import io.circe.yaml.parser // Your YAML content val ymlText = """ ALL: Category1: Subcategory11 : 1.5 Subcategory12 : 0 Subcategory13 : 0 Subcategory14 : 0.5 Category2: Subcategory21 : 1.5 Subcategory22 : 0.3 Subcategory23 : 0 Subcategory24 : 0 """ // Parse YAML to Json (use valueOr to handle parsing errors cleanly, avoid null!) val json: Json = parser.parse(ymlText).valueOr(throw _)
Now, let's safely filter out subcategories with a value of 0. We'll use circe's type-safe extraction methods instead of risky casts:
// Extract the "ALL" object from the JSON val allCategories = json.hcursor.downField("ALL").focus.getOrElse(Json.obj()) // Filter out subcategories with value 0 (handle all numeric types safely) val filteredCategories = allCategories.as[Map[String, Map[String, Json]]].valueOr(throw _).map { case (categoryName, subcategories) => val filteredSubcats = subcategories.filter { case (subcatName, value) => // Convert the Json value to Double safely, then check if it's > 0 value.as[Double].fold( _ => false, // Ignore non-numeric values if any num => num > 0.0 ) } (categoryName, filteredSubcats) } // Print the result println(filteredCategories)
Alternative: Direct Numeric Type Checking
If you prefer to work with circe's Number type directly (to avoid converting to Double unnecessarily), you can do this:
val filteredCategoriesAlt = allCategories.asObject.get.toMap.map { case (categoryName, subcatsJson) => val filteredSubcats = subcatsJson.asObject.get.toMap.filter { case (subcatName, value) => value.asNumber match { case Some(num) => // Check if the number is greater than 0 (works for BigInt and BigDecimal) num.toDouble.exists(_ > 0.0) case None => false // Skip non-numeric entries } } (categoryName, filteredSubcats) } println(filteredCategoriesAlt)
Key Improvements Over Your Original Code
- No risky casts: We use circe's
asandasNumbermethods to handle type conversions safely. - Avoid nulls: Using
valueOrandgetOrElseensures we handle missing values gracefully instead of relying onnull. - Idiomatic Scala: Using
valinstead ofvaraligns with functional programming practices.
Expected Output
Both solutions will produce the filtered result:
Map( Category1 -> Map(Subcategory11 -> 1.5, Subcategory14 -> 0.5), Category2 -> Map(Subcategory21 -> 1.5, Subcategory22 -> 0.3) )
内容的提问来源于stack exchange,提问作者BrDATA

