All Downloads are FREE. Search and download functionalities are using the official Maven repository.

zio.json.ast.JsonUtils.scala Maven / Gradle / Ivy

/*
 * Copyright 2022-2023 Alberto Paro
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package zio.json.ast

import zio.Chunk
import zio.common.TraverseUtils
import zio.json._

import java.time.{ LocalDateTime, OffsetDateTime }

object JsonUtils {

  def resolveSingleField[T: JsonDecoder](
    json: Json,
    field: String
  ): Option[Either[String, T]] =
    resolveFieldMultiple(json, field).headOption

  def resolveFieldMultiple[T: JsonDecoder](
    json: Json,
    field: String
  ): Chunk[Either[String, T]] = {
    val tokens             = field.split('.')
    var terms: Chunk[Json] = keyValues(tokens.head, json)
    tokens.tail.foreach(k => terms = terms.flatMap(j => keyValues(k, j)))
    terms.map(_.as[T])

  }

  private def keyValues(key: String, json: Json): Chunk[Json] =
    json match {
      case Json.Obj(fields)   => fields.filter(_._1 == key).map(_._2)
      case Json.Arr(elements) => elements.flatMap(o => keyValues(key, o))
      case _                  => Chunk.empty
    }

  def mergeJsonList(items: List[Json]): Json = {
    var json: Json = Json.Obj()
    items.foreach { value =>
      json = json.deepMerge(value)
    }
    json
  }

  def mapToJson(map: Map[_, _]): Json =
    Json.Obj(map.toList.map { v =>
      v._1.toString -> anyToJson(v._2)
    }: _*)

  def anyToJson(value: Any): Json = value match {
    case v: Json    => v
    case None       => Json.Null
    case null       => Json.Null
    case Nil        => Json.Arr()
    case Some(x)    => anyToJson(x)
    case b: Boolean => Json.Bool(b)
    case i: Int     => Json.Num(i)
    case i: Long    => Json.Num(i)
    case i: Float   => Json.Num(i)
    case i: Double  => Json.Num(i)
    //    case i: BigDecimal => JsNumber(i)
    case s: String         => Json.Str(s)
    case s: OffsetDateTime => Json.Str(s.toString)
    case s: LocalDateTime  => Json.Str(s.toString)
    case x: Seq[_]         => Json.Arr(x.map(anyToJson): _*)
    case x: Set[_]         => Json.Arr(x.map(anyToJson).toList: _*)
    case x: Map[_, _] =>
      Json.Obj(x.map { case (k, v) => k.toString -> anyToJson(v) }.toSeq: _*)
    case default =>
      throw InvalidJsonValue(s"$default not valid")
  }

  def jsToAny(value: Json): Any =
    value match {
      case Json.Obj(fields) =>
        fields.toMap.map { case (name, jval) =>
          name -> jsToAny(jval)
        }
      case Json.Arr(elements) => elements.map(p => jsToAny(p))
      case Json.Bool(value)   => value
      case Json.Str(value)    => value
      case Json.Num(value)    => value
      case Json.Null          => null
    }

  def jsClean(fields: (String, Json)*): Json =
    Json.Obj(fields.filterNot(_._2 == Json.Null): _*)

  def cleanValue(json: Json): Json =
    json match {
      case Json.Obj(fields) =>
        if (fields.isEmpty)
          Json.Null
        else
          joClean(json)

      case Json.Arr(elements) =>
        val value = elements
        if (value.isEmpty) {
          Json.Null
        } else {
          Json.Arr(
            value.map { curval =>
              cleanValue(curval)
            }.filterNot(_ == Json.Null)
          )
        }

      case Json.Str(value) =>
        val s     = value
        val strim = s.trim
        if (strim.isEmpty)
          Json.Null
        else if (s == strim)
          Json.Str(value)
        else
          Json.Str(strim)

      case x => x
    }

  def joClean(json: Json): Json = json match {
    case Json.Obj(oFields) =>
      val fields = oFields.map(v => v._1 -> cleanValue(v._2)).filterNot(_._2 == Json.Null)
      if (fields.isEmpty) {
        Json.Null
      } else {
        Json.Obj(fields)
      }
    case x => x
  }

  /**
   * These functions are used to create Json objects from filtered sequences of
   * (String, Json) tuples. When the Json in a tuple is Json.Null or JsUndefined
   * or an empty Json.Obj, that tuple is considered not valid, and will be
   * filtered out. Create a Json from `value`, which is valid if the `isValid`
   * function applied to `value` is true.
   */
  def toJsonIfValid[T](value: T, isValid: T => Boolean)(implicit
    enc: JsonEncoder[T]
  ): Either[String, Json] =
    if (isValid(value)) enc.toJsonAST(value) else Right(Json.Null)

  def toJsonIfFull[T](value: Seq[T])(implicit enc: JsonEncoder[T]): Either[String, Json] =
    if (value.nonEmpty) TraverseUtils.traverse(value.map(enc.toJsonAST)).map(r => Json.Arr(r: _*)) else Right(Json.Null)

  def toJsonIfFull[T, S](value: Seq[T], xform: T => S)(implicit
    enc: JsonEncoder[S]
  ): Either[String, Json] =
    if (value.nonEmpty) {
      TraverseUtils.traverse(value.map(xform.andThen(enc.toJsonAST))).map(r => Json.Arr(r: _*))
    } else Right(Json.Null)

  /**
   * Create a Json from `xform` applied to `value`, which is valid if the
   * `isValid` function applied to `value` is true.
   */
  def toJsonIfValid[T, S](value: T, isValid: T => Boolean, xform: T => S)(implicit
    enc: JsonEncoder[S]
  ): Either[String, Json] =
    if (isValid(value)) enc.toJsonAST(xform(value)) else Right(Json.Null)

  /**
   * Create a Json from `value`, which is valid if `value` is not equal to
   * `default`.
   */
  def toJsonIfNot[T](value: T, default: T)(implicit enc: JsonEncoder[T]): Either[String, Json] =
    if (value != default) enc.toJsonAST(value) else Right(Json.Null)

  /**
   * Create a Json from `xform` applied to `value`, which is valid if `value` is
   * not equal to `default`.
   */
  def toJsonIfNot[T, S](value: T, default: T, xform: T => S)(implicit
    enc: JsonEncoder[S]
  ): Either[String, Json] =
    if (value != default) enc.toJsonAST(xform(value)) else Right(Json.Null)

  /**
   * Determines if a property (String, Json) is valid, by testing the Json in
   * the second item.
   */
  def isValidJsonProperty(property: (String, Json)): Boolean =
    property._2 match {
      case Json.Obj(fields)   => fields.nonEmpty
      case Json.Arr(elements) => elements.nonEmpty
      case Json.Bool(value)   => value
      case Json.Null          => false
      case _                  => true
    }

  /**
   * Filters a series of properties, keeping only the valid ones.
   */
  def filterValid(properties: (String, Json)*) =
    properties.filter(isValidJsonProperty)

  /**
   * Create a Json object by filtering a series of properties.
   */
  def toJsonObject(properties: (String, Json)*): Json =
    Json.Obj(filterValid(properties: _*): _*)

  /**
   * Drop the entries with a null value if this is an object or array.
   */
//  def deepDropEmptyValues(src: Json): Json = {
//    val folder = new Json.Folder[Json] {
//      def onNull: Json = Json.Null
//      def onBoolean(value: Boolean): Json = Json.Bool(value)
//      def onNumber(value: JsonNumber): Json = Json.fromJsonNumber(value)
//      def onString(value: String): Json = Json.Str(value)
//      def onArray(value: Vector[Json]): Json = {
//        val values = value.collect {
//          case v if !v.isNull => v.foldWith(this)
//        }
//        if (values.nonEmpty)
//          Json.Arr(values)
//        else
//          Json.Null
//      }
//
//      def onObject(value: Json.Obj): Json = {
//        val jsonObject = value.filter { case (_, v) => !v.isNull }.mapValues(_.foldWith(this))
//        if (jsonObject.nonEmpty)
//          Json.fromJsonObject(jsonObject)
//        else
//          Json.Null
//      }
//    }
//
//    src.foldWith(folder).deepDropNullValues
//  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy