Scala в целом не рекомендует использовать downcasting, и Play Json в этом отношении идиоматичен. Приведение в исходное состояние является проблемой, поскольку компилятор не может отследить возможность неправильного ввода или других ошибок. Получив значение типа Map[String, Any]
, вы сами - компилятор не может помочь вам отследить, какими могут быть эти значения Any
.
У вас есть пара альтернатив. Первый - использовать операторы пути для перехода к определенной точке дерева, где вы знаете тип:
scala> val json = Json.parse(jsonString)
json: play.api.libs.json.JsValue = {"key1": ...
scala> val k1Value = (json \ "key1" \ "subkey1" \ "k1").validate[String]
k1Value: play.api.libs.json.JsResult[String] = JsSuccess(value1,)
Это похоже на что-то вроде следующего:
val json: Map[String, Any] = ???
val k1Value = json("key1")
.asInstanceOf[Map[String, Any]]("subkey1")
.asInstanceOf[Map[String, String]]("k1")
Но прежний подход имеет то преимущество, что он терпит неудачу способами, которые легче рассуждать. Вместо потенциально трудного для интерпретации ClassCastException
исключения , мы просто получили бы хорошее значение JsError
.
Обратите внимание, что мы можем проверить в точке выше в дереве, если мы знаем, какую структуру мы ожидаем:
scala> println((json \ "key2").validate[List[Map[String, String]]])
JsSuccess(List(Map(j1 -> v1, j2 -> v2), Map(j1 -> x1, j2 -> x2)),)
Оба эти примера Play основаны на концепции классы типов - и, в частности, в экземплярах классов типов Read
, предоставляемых Play. Вы также можете предоставить свои собственные экземпляры классов типов для типов, которые вы определили сами. Это позволит вам сделать что-то вроде следующего:
val myObj = json.validate[MyObj].getOrElse(someDefaultValue)
val something = myObj.key1.subkey1.k2(2)
Или что угодно. Документация Play (ссылка выше) дает хорошее представление о том, как это сделать, и вы всегда можете задать дополнительные вопросы здесь, если у вас возникнут проблемы.
Чтобы обратиться к обновлению в вашем вопросе, можно изменить вашу модель, чтобы учесть различные возможности для key2
, а затем определить свой собственный экземпляр Reads
:
case class MyJson(key1: String, key2: Either[String, Map[String, String]])
implicit val MyJsonReads: Reads[MyJson] = {
val key2Reads: Reads[Either[String, Map[String, String]]] =
(__ \ "key2").read[String].map(Left(_)) or
(__ \ "key2").read[Map[String, String]].map(Right(_))
((__ \ "key1").read[String] and key2Reads)(MyJson(_, _))
}
Какой работает так:
scala> Json.parse(jsonString).as[List[MyJson]].foreach(println)
MyJson(v1,Left(v2))
MyJson(x1,Left(x2))
MyJson(y1,Right(Map(subkey1 -> subval1, subkey2 -> subval2)))
Да, это немного более многословно, но это предварительное многословие, за которое вы платите один раз (и это дает вам хорошие гарантии), а не кучу приведений это может привести к запутанным ошибкам во время выполнения.
Это не для всех, и, возможно, вам не по вкусу - это прекрасно. Вы можете использовать операторы пути для обработки подобных случаев или даже простого старого Джексона. Я бы посоветовал вам дать шанс подходу типа «класс», хотя есть крутая кривая обучения, но многие люди (включая меня) очень сильно это предпочитают.
Хорошо, теперь я понял это. Ответ - поместить подотчет в прямоугольник. Затем установите видимость прямоугольника примерно так:
=IIF(First(Fields![SOMEFEILD].Value, "[SOMEDATASET]") IS NOTHING, TRUE, FALSE)
Где [SOMEDATASET]
- это набор данных, заполняемый так же, как и подотчет. Тогда, если подотчет пуст, то [SOMEDATASET]
также будет пустым, и, что более важно, поле [SOMEFEILD]
будет равно НИЧЕГО
.
Бада-Бинг! Один отчет, который не загроможден пустыми страницами, которые тратят впустую бумагу.
Примечание: у этого подхода есть один плохой побочный эффект, заключающийся в том, что сервер SQL будет отправлять одну и ту же информацию дважды, один раз для заполнения набора данных подотчета, и снова, чтобы заполнить повторяющийся набор данных в отчете. Для меня это приемлемо,