|
Lift Framework example source code file (JSONRecord.scala)
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 |
Copyright 1998-2024 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.