简体   繁体   English

使用Casbah / Salat定义自定义序列化 - 或将序列化委托给成员?

[英]Define custom serialization with Casbah / Salat - or delegate serialization to member?

I'm in the process of learning Scala for a new project having come from Rails. 我正在学习Scala,以获得来自Rails的新项目。 I've defined a type that is going to be used in a number of my models which can basically be thought of as collection of 'attributes'. 我已经定义了一个将在我的模型中使用的类型,它基本上可以被认为是“属性”的集合。 It's basically just a wrapper for a hashmap that delegates most of its responsibilities to it: 它基本上只是一个hashmap的包装器,它将大部分职责委托给它:

case class Description(attributes: Map[String, String]) {

  override def hashCode: Int = attributes.hashCode

  override def equals(other: Any) = other match {
    case that: Description => this.attributes == that.attributes
    case _ => false
  }
}

So I would then define a model class with a Description , something like: 因此,我将使用Description定义一个模型类,如:

case class Person(val name: String, val description: Description)

However, when I persist a Person with a SalatDAO I end up with a document that looks like this: 但是,当我坚持使用SalatDAO的Person ,我最终得到的文档看起来像这样:

{
  name : "Russell",
  description: 
  {
    attributes: 
    {
      hair: "brown",
      favourite_color: "blue"
    }
  }
}

When in actual fact I don't need the nesting of the attributes tag in the description tag - what I actually want is this: 实际上我不需要在description标签中嵌套attributes标签 - 我真正想要的是:

{
  name : "Russell",
  description: 
  {
    hair: "brown",
    favourite_color: "blue"
  }
}

I haven't tried, but I reckon I could get that to work if I made Description extend a Map rather than contain one, but I'd rather not, because a Description isn't a type of Map , it's something which has some of the behaviour of a Map as well as other behaviour of its own I'm going to add later. 我没有尝试过,但我认为如果我使Description扩展一个Map而不是包含一个,我可以得到它,但我宁愿不这样做,因为一个Description不是一种Map ,它有一些东西有一些我将在稍后添加一个Map行为以及它自己的其他行为。 Composition over inheritance and so on. 继承的构成等。

So my question is, how can I tell Salat (or Casbah, I'm actually a bit unclear as to which is doing the conversion as I've only just started using them) how to serialize and deserialize the Description class? 所以我的问题是,我怎么能告诉Salat(或Casbah,我实际上有点不清楚哪些是转换,因为我刚开始使用它们)如何序列化和反序列化Description类? In the casbah tutorial here it says: 这里的casbah教程中它说:

It is also possible to create your own custom type serializers and deserializers. 也可以创建自己的自定义类型序列化器和反序列化器。 See Custom Serializers and Deserializers. 请参阅自定义序列化和反序列化。

But this page doesn't seem to exist. 但是这个页面似乎不存在。 Or am I going about it the wrong way? 或者我是以错误的方式去做的? Is there actually a really simple way to indicate this is what I want to happen, an annotation or something? 实际上是否有一种非常简单的方法来表明这是我想要发生的事情,注释还是什么? Or can I simply delegate the serialization to the attributes map in some way? 或者我可以简单地以某种方式将序列化委托给属性映射?

EDIT: After having a look at the source for the JodaTime conversion helper I've tried the following but have had no luck getting it to work yet: 编辑:看了JodaTime转换助手的源后,我尝试了以下但是没有运气让它工作了:

import org.bson.{ BSON, Transformer }
import com.mongodb.casbah.commons.conversions.MongoConversionHelper

object RegisterCustomConversionHelpers extends Serializers
  with Deserializers {
  def apply() = {
    super.register()
  }
}

trait Serializers extends MongoConversionHelper
  with DescriptionSerializer {

  override def register() = {
    super.register()
  }
  override def unregister() = {
    super.unregister()
  }
}

trait Deserializers extends MongoConversionHelper {
  override def register() = {
    super.register()
  }
  override def unregister() = {
    super.unregister()
  }
}

trait DescriptionSerializer extends MongoConversionHelper {
  private val transformer = new Transformer {
    def transform(o: AnyRef): AnyRef = o match {
      case d: Description => d.attributes.asInstanceOf[AnyRef]
      case _ => o
    }
  }

  override def register() = {
    BSON.addEncodingHook(classOf[Description], transformer)
    super.register()
  }
}

When I call RegisterCustomConversionHelpers() then save a Person I don't get any errors, it just has no effect, saving the document the same way as ever. 当我调用RegisterCustomConversionHelpers()然后保存一个Person我没有得到任何错误,它只是没有效果,以同样的方式保存文档。 This also seems like quite a lot to have to do for what I want. 对于我想要的东西,这似乎也是很多。

Salat maintainer here. Salat维护者在这里。

I don't understand the value of Description as a wrapper here. 我不理解Description作为包装器的值。 It wraps a map of attributes, overrides the default equals and hashcode impl of a case class - which seems unnecessary since the impl is delegated to the map anyhow and that is exactly what the case class does anyway - and introduces an additional layer of indirection to the serialized object. 它包装了一个属性映射,覆盖了一个case类的默认equals和hashcode impl - 这似乎是不必要的,因为impl无论如何都被委托给了map,而这正是case类所做的 - 并引入了一个额外的间接层序列化对象。

Have you considered just: 你考虑过:

case class Person(val name: String, val description: Map[String, String])

This will do exactly what you want out of box. 这将完全符合您的要求。

In another situation I would recommend a simple type alias but unfortunately Salat can't support type aliases right now due to some issues with how they are depicted in pickled Scala signatures. 在另一种情况下,我会推荐一个简单的类型别名,但不幸的是Salat现在不能支持类型别名,因为它们在腌制的Scala签名中描述了它们的一些问题。

(You probably omitted this from your example from brevity, but it is best practice for your Mongo model to have an _id field - if you don't, the Mongo Java driver will supply one for you) (你可能从简洁的例子中省略了这个,但最好的做法是你的Mongo模型有一个_id字段 - 如果不这样做,Mongo Java驱动程序会为你提供一个)

There is a working example of a custom BSON hook in the salat-core test package (it handles java.net.URL). 在salat-core测试包中有一个自定义BSON钩子的工作示例(它处理java.net.URL)。 It could be that your hook is not working simply because you are not registering it in the right place? 可能是因为你没有在正确的地方注册它而你的钩子不能正常工作? But still, I would recommend getting rid of Description unless it is adding some value that is not evident from your example above. 但是,我仍然建议删除描述,除非它添加一些从上面的示例中不明显的值。

Based on @prasinous' answer I decided this wasn't going to be that easy so I've changed my design a bit to the following, which pretty much gets me what I want. 根据@prasinous的回答,我决定这不会那么容易,所以我把设计改为以下,这几乎让我得到了我想要的东西。 Rather than persisting the Description as a field I persist a vanilla map then mix in a Described trait to the model classes I want to have a description, which automatically converts the map to Description when the object is created. 我没有将Description作为字段持久化,而是持久保存了一个vanilla地图,然后将Described特征混合到我想要Described的模型类中,该Description在创建对象时自动将地图转换为Description Would appreciate it if anyone can point out any obvious problems to this approach or any suggestions for improvement. 如果有人能指出这种方法的任何明显问题或任何改进建议,将不胜感激。

class Description(val attributes: Map[String, String]){
  //rest of class omitted
}

trait Described {
  val attributes: Map[String, String]
  val description = new Description(attributes)
}

case class Person(name: String, attributes: Map[String, String]) extends Described

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM