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

com.rojoma.json.v3.util.SimpleHierarchyDecodeBuilder.scala Maven / Gradle / Ivy

The newest version!
package com.rojoma.json.v3
package util

import scala.reflect.ClassTag

import ast._
import codec._

import com.rojoma.json.v3.`-impl`.util.ClassAwareMap

class SimpleHierarchyDecodeBuilder[Root <: AnyRef] private[util] (tagType: TagType, subcodecs: Map[String, JsonDecode[_ <: Root]], classes: ClassAwareMap[String]) {
  def branch[T <: Root](name: String)(implicit dec: JsonDecode[T], mfst: ClassTag[T]) = {
    val cls = mfst.runtimeClass
    if(subcodecs contains name) throw new IllegalArgumentException("Already defined a decoder for branch " + name)
    if(classes containsExact cls) throw new IllegalArgumentException("Already defined a decoder for class " + cls)
    new SimpleHierarchyDecodeBuilder[Root](tagType, subcodecs + (name -> dec), classes + (cls -> name))
  }

  def build: JsonDecode[Root] = {
    if(subcodecs.isEmpty) throw new IllegalStateException("No branches defined")
    tagType match {
      case TagToValue =>
        new JsonDecode[Root] {
          def decode(x: JValue): Either[DecodeError, Root] = x match {
            case JObject(fields) =>
              // this should almost always pick the first branch
              val pendingErrors = List.newBuilder[DecodeError]
              if(fields.size <= subcodecs.size) {
                for {
                  (possibleTag, possibleObject) <- fields
                  subdec <- subcodecs.get(possibleTag)
                } {
                  subdec.decode(possibleObject) match {
                    case r@Right(_) => return r
                    case Left(err) => pendingErrors += err.prefix(possibleTag)
                  }
                }
              } else {
                for {
                  (tag, subdec) <- subcodecs
                  field <- fields.get(tag)
                } {
                  subdec.decode(field) match {
                    case r@Right(_) => return r
                    case Left(err) => pendingErrors += err.prefix(tag)
                  }
                }
              }
              val errorsFound = pendingErrors.result()
              if(errorsFound.nonEmpty) Left(DecodeError.join(errorsFound))
              else Left(DecodeError.Multiple(subcodecs.keys.toSeq.map(DecodeError.MissingField(_))))
            case other =>
              Left(DecodeError.InvalidType(expected = JObject, got = other.jsonType))
          }
        }
      case TagAndValue(typeField, valueField) =>
        new JsonDecode[Root] {
          def decode(x: JValue): Either[DecodeError, Root] = x match {
            case JObject(fields) =>
              fields.get(typeField) match {
                case Some(jTypeTag@JString(typeTag)) =>
                  subcodecs.get(typeTag) match {
                    case Some(subDec) =>
                      fields.get(valueField) match {
                        case Some(jvalue) =>
                          subDec.decode(jvalue) match {
                            case r@Right(_) => r
                            case Left(err) => Left(err.prefix(valueField))
                          }
                        case None =>
                          Left(DecodeError.MissingField(valueField))
                      }
                    case None =>
                      Left(DecodeError.InvalidValue(jTypeTag, Path(typeField)))
                  }
                case Some(other) =>
                  Left(DecodeError.InvalidType(expected = JString, got = other.jsonType, Path(typeField)))
                case None =>
                  Left(DecodeError.MissingField(typeField))
              }
            case other =>
              Left(DecodeError.InvalidType(expected = JObject, got = other.jsonType, Path.empty))
          }
        }
      case InternalTag(typeField, removeForSubcodec) =>
        new JsonDecode[Root] {
          def decode(x: JValue): Either[DecodeError, Root] = x match {
            case JObject(fields) =>
              fields.get(typeField) match {
                case Some(jTypeTag@JString(typeTag)) =>
                  subcodecs.get(typeTag) match {
                    case Some(subDec) =>
                      subDec.decode(if(removeForSubcodec) JObject(fields-typeField) else x) // no need to augment error results since we're not moving downward
                    case None =>
                      Left(DecodeError.InvalidValue(jTypeTag, Path(typeField)))
                  }
                case Some(other) =>
                  Left(DecodeError.InvalidType(expected = JString, got = other.jsonType, Path(typeField)))
                case None =>
                  Left(DecodeError.MissingField(typeField))
              }
            case other =>
              Left(DecodeError.InvalidType(expected = JObject, got = other.jsonType))
          }
        }
    }
  }
}

class NoTagSimpleHierarchyDecodeBuilder[Root <: AnyRef] private[util] (subcodecs: Seq[(Class[_], JsonDecode[_ <: Root])]) {
  def branch[T <: Root](implicit dec: JsonDecode[T], mfst: ClassTag[T]) = {
    val cls = mfst.runtimeClass
    if(subcodecs.find(_._1 == cls).isDefined) throw new IllegalArgumentException("Already defined a codec for class " + cls)
    new NoTagSimpleHierarchyDecodeBuilder[Root](subcodecs :+ (cls -> dec))
  }

  def build: JsonDecode[Root] = {
    if(subcodecs.isEmpty) throw new IllegalStateException("No branches defined")
    new JsonDecode[Root] {
      def decode(x: JValue): Either[DecodeError, Root] = {
        val accumulatedErrors = List.newBuilder[DecodeError]
        for((_, subDec) <- subcodecs) {
          subDec.decode(x) match {
            case r@Right(_) => return r
            case Left(e) => accumulatedErrors += e
          }
        }
        Left(DecodeError.join(accumulatedErrors.result()))
      }
    }
  }
}

object SimpleHierarchyDecodeBuilder {
  def apply[Root <: AnyRef](tagType: TagType) = new SimpleHierarchyDecodeBuilder[Root](tagType, Map.empty, ClassAwareMap.empty)
  def apply[Root <: AnyRef](tagType: NoTag) = new NoTagSimpleHierarchyDecodeBuilder[Root](Vector.empty)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy