Работа с серализированными данными
Contents
Работа с серализированными данными#
JSON#
JObject#
Представляет собой обертку над мапой ключ-значение Отлично подходит для небольших объектов ключ-значение и для больших объектов без конкретной структуры
Способы получения#
val fromStr = "".ns.toJValue.asJObject
val fromRop = JEmbeddedDoc.parseProperty(rop, "Имя json контейнера").asJObject
Геттеры#
Для получения данных из контейнера по ключу используйте один из типизированных геттеров
ru.bitec.app.gtk.lang.json.JObjectAbs
:val jObj = JEmbeddedDoc.parseProperty(rop,"Имя json контейнера").asJObject jObj.getNString(key) jObj.getBoolean(key) jObj.getNLong(key) jObj.getNDate(key) jObj.getNGid(key) jObj.childJObject(key) jObj.childJArray(key) /** Общий гетер * ВАЖНО: JArray и JObject полученные через этот метод являются копиями т.е. изменеия в них не отобразятся в ориг. объекте */ jObj.getJValue(key: NString)
Для записи в контейнер используйте типизированные сеттеры
ru.bitec.app.gtk.lang.json.JObjectAbs
:val jObj = "{}".ns.toJValue.asJObject jObj.set("key".ns, value) val innerJArray = jObj.createJArray("array".ns) val innerJObject = jObj.createJObject("object".ns)
Object Mapper#
Конвертирует JSON строку в конкретные объекты
Cериализация и десериализация#
val jsonString = """{"key":{"id":10,"value":"someValue"}}"""
/** ВАЖНО все классы используемые для десериализации должны
* быть объявлены вне других классов
*/
case class Example(id: NLong, value: NString)
val map = GtkObjectMapper().readValue[Map[NString, Example]](jsonString)
val newJsonString = GtkObjectMapper().writeValueAsString(map)
Cериализация и десериализация абстрактных типов#
// При сериализации наследников им добавится ключ "type"
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = As.PROPERTY,
property = "type")
@JsonSubTypes(
value = Array(
new JsonSubTypes.Type(value = classOf[Type1], name = "type1"),
new JsonSubTypes.Type(value = classOf[Type2], name = "type2")
)
)
trait Base {
def id: NLong
def sValue: NString
}
@JsonTypeName("type1")
case class Type1(id: NLong, sValue: NString, nValue: NNumber) extends Base
@JsonTypeName("type2")
case class Type2(id: NLong, sValue: NString, gidValue: NGid) extends Base
val sJSON ="""{
"id": 10,
"sValue": "Hello world",
"type": "type2",
"gidValue": "0/0"
}"""
val result = GtkObjectMapper().readValue[Base](sJSON) match {
case Type1(id, sValue, nValue) => nValue.toString()
case Type2(id, sValue, gidValue) => gidValue.toString
}
XML#
scala.XML#
Сериализация и десериализация#
// Получить их строки
val xmlText = """*XML строка*"""
val xml = XML.loadString(xmlText)
// задать напрямую в коде, в '{}' можно указывать выражения которые возвращают текст или элементы
val xml = <Node>
{Seq(1,2,3).map(num => <Node>{num}</Node>)}
</Node>
// задать напрямую в коде (scala 3)
val xml = xml"""<Node></Node>"""
// Превратить обратно в строку
val xmlText = xml.toString
Получение значений#
val xml =
<example>
<tag name="Name"/>
<children>
<child name="1"/>
<child name="2"/>
<child name="3"/>
<child name="3">Hello</child>
</children>
</example>
// собирает все ноды в "example" c тегом "children", потом собирает все ноды в "children" c тегом "child"
val children = xml \ "children" \ "child"
// собирает все ноды "child" во всем документе
val children2 = xml \\ "child"
// '\@' берет атрибут у элемента и возвращает его строковое значение
// ВАЖНО если элементов несколько то сложит их значения в одну строку, используйте если уверены что работаете с 1 элементом
val firstChildrenName = children.headOption.toSeq \@ "name"
// если нужно просто получить значения в виде списка то используйте '\' и добавте "@" к названию атрибута
val childrenNames = (children \ "@name").map(_.text)
Cериализация и десериализация в объекты#
Автоматической конвертации в объекты в библиотеке нет, и лучший вариант писать самому
case class Child(sName: NString, sText: NString) {
def toXML(): Node = {
// Если в значение атрибута передать null, то атрибут будет удален из элемента
<child name={sName.get}>
{sText.get}
</child>
.copy(minimizeEmpty = true) // складывает Node типа <child></child> в <child/>
}
}
object Child {
def fromXML(node: Node): Child = {
Child(
/*
* Осторожнее с .text
* 1) Он складывает все текстовые ноды в одну строку
* 2) '\' и '\\' возвращают пустые NodeSeq если ничего не нашли,
* и .text вернет пустую строку, что не всегда желательно
*/
sName = (node \ "@name").text.nullif(""),
sText = node.text.nullif("")
)
}
}
// <child name="Joe">Hello world</child>
val exampleTo1 = Child(sName = "Joe", "Hello world").toXML()
// <child/>
val exampleTo2 = Child(sName = None.ns, None.ns).toXML()
Обработка XML#
val xml =
<Отчет>
<Документы>
<Документ имя="Дог №1">
<Сумма тип="Поступление">5000</Сумма>
<Сумма тип="Расходы">100</Сумма>
</Документ>
<Документ имя="Дог №4">
<Сумма тип="Поступление">200</Сумма>
<Сумма тип="Расходы">30</Сумма>
</Документ>
<Документ имя="Дог №2">
<Сумма тип="Расходы">100</Сумма>
</Документ>
</Документы>
</Отчет>
// Правило которое для элемента "Сумма" c типом "Поступление" разделит сумму указанную в нем на 2
object subtractTAX extends RewriteRule {
override def transform(n: Node): Seq[Node] = n match {
case elem: Elem if elem.label == "Сумма" && (elem \ "@тип").text == "Поступление" =>
val nCurrSum = NNumber.fromAny(elem.text)
val nSumAfterTAX = (nCurrSum * 50.nn / 100.nn).round(2)
elem.copy(child = Text(nSumAfterTAX.toString))
case rest => rest
}
}
// Правило которое для элемента "Документ" посчитает сумму указанную в элементах "Сумма"
object finalDocSum extends RewriteRule {
override def transform(n: Node): Seq[Node] = n match {
case elem: Elem if elem.label == "Документ" =>
val sum = elem.descendant.collect {
case elem: Elem if elem.label == "Сумма" && (elem \ "@тип").text == "Поступление" =>
NNumber.fromAny(elem.text).nvl(0.nn)
}.fold(0.nn)(_ + _)
elem % Attribute(None, "сумма", Text(sum.toString), Null)
case rest => rest
}
}
// Правило которое для элемента "Отчет" посчитает сумму указанную в атрибутах для элементов "Документ"
object finalReportSum extends RewriteRule {
override def transform(n: Node): Seq[Node] = n match {
case elem: Elem if elem.label == "Отчет" =>
val nFinalSum = (elem \\ "Документ")
.map(n => (n \ "@сумма").text)
.map(sSum => NNumber.fromAny(sSum).nvl(0.nn))
.fold(0.nn)(_ + _)
elem % Attribute(None, "сумма", Text(nFinalSum.toString), Null)
case rest => rest
}
}
// собираем общее правило в порядке в котором мы хотим чтобы документ обработался
val ruleTransformer = new RuleTransformer(subtractTAX, finalDocSum, finalReportSum)
/*
<Отчет сумма="2600">
<Документы>
<Документ сумма="2500" имя="Дог №1">
<Сумма тип="Поступление">2500</Сумма>
<Сумма тип="Расходы">100</Сумма>
</Документ>
<Документ сумма="100" имя="Дог №4">
<Сумма тип="Поступление">100</Сумма>
<Сумма тип="Расходы">30</Сумма>
</Документ>
<Документ сумма="0" имя="Дог №2">
<Сумма тип="Расходы">100</Сумма>
</Документ>
</Документы>
</Отчет>
*/
val xmlResult = ruleTransformer(xml)