Работа с серализированными данными#

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)