JSON解析失败:如何定义适配单层/嵌套列表的响应Case类?
Got it, let's break down your problem first: your API returns inconsistent JSON structures for the same field—sometimes it's a nested list like [[x,y], [a,b]] (multiple data pairs), other times it's a flat list like [x,y] (just one pair). Your current case class is rigidly typed to one of these structures, so when the API switches to the other format, the parser throws an exception.
Here's how to fix this, using two popular Scala JSON libraries (Play JSON and Circe) since you mentioned case classes. I'll assume your data pairs are made of two values (like strings, adjust the types to match your actual data):
First, Define Your Core Case Classes
Let's start with the end goal: we want to normalize both JSON formats into a single ApiResponse case class that holds a list of DataPair instances:
// Replace String with your actual data types (Int, Double, etc.) case class DataPair(first: String, second: String) case class ApiResponse(data: List[DataPair])
Solution 1: Using Play JSON
Play JSON lets you define custom Reads instances to handle edge cases like this. The idea is to first try parsing the nested list format, and if that fails, fall back to parsing the flat list format:
import play.api.libs.json._ // Basic helper to convert a 2-element list to a DataPair (with validation) def listToDataPair(list: List[String]): JsResult[DataPair] = { if (list.length == 2) JsSuccess(DataPair(list(0), list(1))) else JsError("Data pair must be a list of exactly two elements") } // Custom Reads for ApiResponse that handles both formats implicit val apiResponseReads: Reads[ApiResponse] = Reads { json => // First attempt: parse as nested list (multiple pairs) json.validate[List[List[String]]].flatMap { nestedPairs => // Convert each nested list to a DataPair, collect valid results JsResult.traverse(nestedPairs)(listToDataPair).map(ApiResponse(_)) } // If nested parsing fails, try parsing as a single flat list (one pair) .recoverWith { _ => json.validate[List[String]].flatMap { singlePair => listToDataPair(singlePair).map(pair => ApiResponse(List(pair))) } } }
Testing It
- For nested JSON:
{"data": [["x", "y"], ["a", "b"]]}→ parses toApiResponse(List(DataPair("x","y"), DataPair("a","b"))) - For flat JSON:
{"data": ["x", "y"]}→ parses toApiResponse(List(DataPair("x","y")))
Solution 2: Using Circe
If you're using Circe (another popular Scala JSON library), the approach is similar—define a custom Decoder that handles both formats:
import io.circe._ import io.circe.generic.semiauto._ // Basic helper to convert a 2-element list to a DataPair (with validation) def listToDataPair(list: List[String]): Decoder.Result[DataPair] = { if (list.length == 2) Right(DataPair(list(0), list(1))) else Left(DecodingFailure("Data pair must be a list of exactly two elements", Nil)) } // Custom Decoder for ApiResponse implicit val apiResponseDecoder: Decoder[ApiResponse] = Decoder.instance { c => // Try nested list first c.as[List[List[String]]].flatMap { nestedPairs => nestedPairs.traverse(listToDataPair).map(ApiResponse(_)) } // Fall back to flat list if nested parsing fails .orElse { c.as[List[String]].flatMap { singlePair => listToDataPair(singlePair).map(pair => ApiResponse(List(pair))) } } } // Optional: Encoder if you need to serialize back to JSON implicit val apiResponseEncoder: Encoder[ApiResponse] = deriveEncoder[ApiResponse]
Why Your Original Code Failed
Chances are your original case class was typed to either List[List[T]] or List[T]—when the API returned the opposite format, the parser couldn't map the JSON structure to your case class, hence the Exception in thread "main" error. The custom parsers above normalize both formats into the same List[DataPair] field, so your application code doesn't have to handle the inconsistency.
内容的提问来源于stack exchange,提问作者binshi

