Ну, есть несколько способов приблизиться к этому. Я бы, вероятно, просто использовал Map[String, Any]
, который должен прекрасно работать для ваших целей (если карта имеет значение collection.immutable
, а не collection.mutable
). Однако, если вы действительно хотите пройти через боль, можно указать для этого:
sealed trait InnerData[+A] {
val value: A
}
case class InnerString(value: String) extends InnerData[String]
case class InnerMap[A, +B](value: Map[A, B]) extends InnerData[Map[A, B]]
case class InnerBoolean(value: Boolean) extends InnerData[Boolean]
Теперь, предполагая, что вы читали поле JSON data
в поле Scala с именем jsData
, вы бы дали этому полю следующий тип:
val jsData: Map[String, Either[Int, InnerData[_]]
Каждый раз, когда вы извлекаете поле из jsData
, вам нужно будет сопоставить шаблон, проверяя, имеет ли значение тип Left[Int]
или Right[InnerData[_]]
(два подтипа Either[Int, InnerData[_]]
). Как только у вас есть внутренние данные, вы должны сопоставить шаблон с с , чтобы определить, представляет ли он InnerString
, InnerMap
или InnerBoolean
.
Технически, вы все равно должны выполнить такой тип сопоставления с образцом, чтобы использовать данные после извлечения их из JSON. Преимущество хорошо сформулированного подхода в том, что компилятор проверит вас, чтобы убедиться, что вы не упустили никаких возможностей. Недостатком является то, что вы не можете просто пропустить невозможности (например, 'eggs'
сопоставление с Int
). Кроме того, все эти объекты-обертки накладывают некоторые накладные расходы, так что следите за этим.
Обратите внимание, что Scala позволяет вам определять псевдоним типа, который должен сократить количество LoC, необходимое для этого:
type DataType[A] = Map[String, Either[Int, InnerData[A]]]
val jsData: DataType[_]
Добавьте несколько неявных преобразований, чтобы сделать API красивым, и вы все должны быть хорошими и модными.