alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Lift Framework example source code file (JSONRecord.scala)

This example Lift Framework source code file (JSONRecord.scala) is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Java - Lift Framework tags/keywords

baserecord, box, box, jarray, jsonmetarecord, jsonrecord, jvalue, jvalue, list, list, manifest, math, ownertype, ownertype, preferences, string, util

The Lift Framework JSONRecord.scala source code

/*
 * Copyright 2010-2011 WorldWide Conferencing, LLC
 *
 * 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 net.liftweb
package couchdb

import java.math.MathContext
import java.util.Calendar
import scala.collection.immutable.TreeSet
import scala.reflect.Manifest
import scala.xml.NodeSeq
import net.liftweb.common.{Box, Empty, Failure, Full}
import Box.{box2Iterable, option2Box}
import net.liftweb.http.js.{JsExp, JsObj}
import net.liftweb.json.JsonParser
import net.liftweb.json.JsonAST._
import net.liftweb.record.{Field, MandatoryTypedField, MetaRecord, Record}
import net.liftweb.record.RecordHelpers.jvalueToJsExp
import net.liftweb.record.FieldHelpers.expectedA
import net.liftweb.record.field._
import net.liftweb.util.ThreadGlobal
import net.liftweb.util.Helpers._
import java.util.prefs.BackingStoreException

private[couchdb] object JSONRecordHelpers {

  /** Remove duplicate fields, preferring the first field seen with a given name */
  def dedupe(fields: List[JField]): List[JField] = {
    var seen = TreeSet.empty[String]
    fields.filter {
      case JField(name, _) if seen contains name => false
      case JField(name, _) => { seen = seen + name; true }
    }
  }
}

import JSONRecordHelpers._

/** Specialized Record that can be encoded and decoded from JSON */
trait JSONRecord[MyType <: JSONRecord[MyType]] extends Record[MyType] {
  self: MyType =>
  
  private var _additionalJFields: List[JField] = Nil

  /** Refines meta to require a JSONMetaRecord */
  def meta: JSONMetaRecord[MyType]

  /** Extra fields to add to the encoded object, such as type. Default is none (Nil) */
  def fixedAdditionalJFields: List[JField] = Nil

  /**
   * Additional fields that are not represented by Record fields, nor are fixed additional fields.
   * Default implementation is for preserving unknown fields across read/write
   */
  def additionalJFields: List[JField] = _additionalJFields

  /**
   * Handle any additional fields that are not represented by Record fields when decoding from a JObject.
   * Default implementation preserves the fields intact and returns them via additionalJFields
   */
  def additionalJFields_= (fields: List[JField]): Unit = _additionalJFields = fields



 /**
  * Save the instance and return the instance
  */
  override def saveTheRecord(): Box[MyType] = throw new BackingStoreException("JSON Records don't save themselves")
}

object JSONMetaRecord {
  /** Local override to record parsing that can cause extra fields to be ignored, even if they otherwise would cause a failure */
  object overrideIgnoreExtraJSONFields extends ThreadGlobal[Boolean]

  /** Local override to record parsing that can cause missing fields to be ignored, even if they otherwise would cause a failure */
  object overrideNeedAllJSONFields extends ThreadGlobal[Boolean]
}

/** Specialized MetaRecord that deals with JSONRecords */
trait JSONMetaRecord[BaseRecord <: JSONRecord[BaseRecord]] extends MetaRecord[BaseRecord] {
  self: BaseRecord =>

  /**
   * Return the name of the field in the encoded JSON object. If the field implements JSONField and has overridden jsonName then
   * that will be used, otherwise the record field name
   */
  def jsonName(field: Field[_, BaseRecord]): String = field match {
    case (jsonField: JSONField) => jsonField.jsonName openOr field.name
    case _                      =>                           field.name
  }

  /** Whether or not extra fields in a JObject to decode is an error (false) or not (true). The default is true */
  def ignoreExtraJSONFields: Boolean = true

  /** Whether or not missing fields in a JObject to decode is an error (false) or not (true). The default is true */
  def needAllJSONFields: Boolean = true

  override def asJSON(inst: BaseRecord): JsObj = jvalueToJsExp(asJValue).asInstanceOf[JsObj]

  override def setFieldsFromJSON(inst: BaseRecord, json: String): Box[Unit] =
    setFieldsFromJValue(inst, JsonParser.parse(json))

  /** Encode a record instance into a JValue */
  override def asJValue(rec: BaseRecord): JObject = {
    val recordJFields = fields(rec).map(f => JField(jsonName(f), f.asJValue))
    JObject(dedupe(recordJFields ++ rec.fixedAdditionalJFields ++ rec.additionalJFields).sortWith(_.name < _.name))
  }

  /** Attempt to decode a JValue, which must be a JObject, into a record instance */
  override def setFieldsFromJValue(rec: BaseRecord, jvalue: JValue): Box[Unit] = {
    def fromJFields(jfields: List[JField]): Box[Unit] = {
      import JSONMetaRecord._

      val flds = fields(rec)
      lazy val recordFieldNames = TreeSet(flds.map(jsonName): _*)
      lazy val jsonFieldNames = TreeSet(jfields.map(_.name): _*)
      lazy val optionalFieldNames = TreeSet(flds.filter(_.optional_?).map(jsonName): _*)
      lazy val recordFieldsNotInJson = recordFieldNames -- jsonFieldNames -- optionalFieldNames
      lazy val jsonFieldsNotInRecord = jsonFieldNames -- recordFieldNames
      
      // If this record type has been configured to be stricter about fields, check those first
      if ((overrideNeedAllJSONFields.box openOr needAllJSONFields) && !recordFieldsNotInJson.isEmpty) {
        Failure("The " + recordFieldsNotInJson.mkString(", ") + " field(s) were not found, but are required.")
      } else if (!(overrideIgnoreExtraJSONFields.box openOr ignoreExtraJSONFields) && !jsonFieldsNotInRecord.isEmpty) {
        Failure("Field(s) " + jsonFieldsNotInRecord.mkString(", ") + " are not recognized.")
      } else {
        for {
          jfield <- jfields
          field  <- flds if jsonName(field) == jfield.name
        } field.setFromJValue(jfield.value)

        rec.additionalJFields = jsonFieldsNotInRecord.toList map {
          name => jfields.find(_.name == name).get
        }

        Full(())
      }
    }

    jvalue match {
      case JObject(jfields) => fromJFields(jfields)
      case other => expectedA("JObject", other)
    }      
  }
}
 
/** Trait for fields with JSON-specific behavior */
trait JSONField {
  /** Return Full(name) to use that name in the encoded JSON object, or Empty to use the same name as in Scala. Defaults to Empty */
  def jsonName: Box[String] = Empty
}


/* ****************************************************************************************************************************************** */


/** Field that contains an entire record represented as an inline object value in the final JSON */
class JSONSubRecordField[OwnerType <: JSONRecord[OwnerType], SubRecordType <: JSONRecord[SubRecordType]]
                        (rec: OwnerType, valueMeta: JSONMetaRecord[SubRecordType])(implicit subRecordType: Manifest[SubRecordType])
  extends Field[SubRecordType, OwnerType] with MandatoryTypedField[SubRecordType]
{
  def this(rec: OwnerType, value: SubRecordType)(implicit subRecordType: Manifest[SubRecordType]) = {
      this(rec, value.meta)
      set(value)
  }

  def this(rec: OwnerType, valueMeta: JSONMetaRecord[SubRecordType], value: Box[SubRecordType])
          (implicit subRecordType: Manifest[SubRecordType]) = {
      this(rec, valueMeta)
      setBox(value)
  }

  def owner = rec
  def asJs = asJValue
  def toForm: Box[NodeSeq] = Empty // FIXME
  def defaultValue = valueMeta.createRecord

  def setFromString(s: String): Box[SubRecordType] = valueMeta.fromJSON(s)

  def setFromAny(in: Any): Box[SubRecordType] = genericSetFromAny(in)

  def asJValue: JValue = valueBox.map(_.asJValue) openOr (JNothing: JValue)
  def setFromJValue(jvalue: JValue): Box[SubRecordType] = jvalue match {
    case JNothing|JNull if optional_? => setBox(Empty)
    case _                            => setBox(valueMeta.fromJValue(jvalue))
  }
}

/** Field that contains an array of some basic JSON type */
class JSONBasicArrayField[OwnerType <: JSONRecord[OwnerType], ElemType <: JValue](rec: OwnerType)(implicit elemType: Manifest[ElemType])
  extends Field[List[ElemType], OwnerType] with MandatoryTypedField[List[ElemType]]
{
  def this(rec: OwnerType, value: List[ElemType])(implicit elemType: Manifest[ElemType]) = { this(rec); set(value) }
  def this(rec: OwnerType, value: Box[List[ElemType]])(implicit elemType: Manifest[ElemType]) = { this(rec); setBox(value) }

  def owner = rec
  def asJs = asJValue
  def toForm: Box[NodeSeq] = Empty // FIXME
  def defaultValue = Nil

  def setFromString(s: String): Box[List[ElemType]] =
    setBox(tryo(JsonParser.parse(s)) flatMap {
      case JArray(values) => checkValueTypes(values)
      case other          => expectedA("JSON string with an array of " + elemType, other)
    })

  def setFromAny(in: Any): Box[List[ElemType]] = genericSetFromAny(in)

  def checkValueTypes(in: List[JValue]): Box[List[ElemType]] =
    in.find(!_.isInstanceOf[ElemType]) match {
      case Some(erroneousValue) if erroneousValue != null =>
          Failure("Value in input array is a " + value.getClass.getName + ", should be a " + elemType.toString)

      case Some(erroneousValue) => Failure("Value in input array is null, should be a " + elemType.toString)
      case None                 => Full(in.map(_.asInstanceOf[ElemType]))
    }

  def asJValue: JValue = valueBox.map(JArray) openOr (JNothing: JValue)
  def setFromJValue(jvalue: JValue): Box[List[ElemType]] = jvalue match {
    case JNothing|JNull if optional_? => setBox(Empty)
    case JArray(values)               => setBox(checkValueTypes(values))
    case other                        => setBox(expectedA("JArray containing " + elemType.toString, other))
  }
}

/** Field that contains a homogeneous array of subrecords */
class JSONSubRecordArrayField[OwnerType <: JSONRecord[OwnerType], SubRecordType <: JSONRecord[SubRecordType]]
                             (rec: OwnerType, valueMeta: JSONMetaRecord[SubRecordType])(implicit valueType: Manifest[SubRecordType])
  extends Field[List[SubRecordType], OwnerType] with MandatoryTypedField[List[SubRecordType]]
{
  def this(rec: OwnerType, value: List[SubRecordType])(implicit subRecordType: Manifest[SubRecordType]) = {
      this(rec, value.head.meta)
      set(value)
  }

  def this(rec: OwnerType, valueMeta: JSONMetaRecord[SubRecordType], value: Box[List[SubRecordType]])
          (implicit subRecordType: Manifest[SubRecordType]) = {
      this(rec, valueMeta)
      setBox(value)
  }

  def owner = rec
  def asJs = asJValue
  def toForm: Box[NodeSeq] = Empty // FIXME
  def defaultValue = Nil

  def setFromString(s: String): Box[List[SubRecordType]] =
    setBox(tryo(JsonParser.parse(s)) flatMap {
      case JArray(values) => fromJValues(values)
      case other          => expectedA("JSON string containing an array of " + valueMeta.getClass.getSuperclass.getName, other)
    })

  def setFromAny(in: Any): Box[List[SubRecordType]] = genericSetFromAny(in)

  private def fromJValues(jvalues: List[JValue]): Box[List[SubRecordType]] =
    jvalues
      .foldLeft[Box[List[SubRecordType]]](Full(Nil)) {
        (prev, cur) => prev.flatMap {
          rest => valueMeta.fromJValue(cur).map(_::rest)
        }
      }
      .map(_.reverse)

  def asJValue: JArray = JArray(value.map(_.asJValue))
  def setFromJValue(jvalue: JValue): Box[List[SubRecordType]] = jvalue match {
    case JNothing|JNull if optional_? => setBox(Empty)
    case JArray(jvalues)              => setBox(fromJValues(jvalues))
    case other                        => setBox(expectedA("JArray containing " + valueMeta.getClass.getSuperclass.getName, other))
  }
}

Other Lift Framework examples (source code examples)

Here is a short list of links related to this Lift Framework JSONRecord.scala source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.