Search code examples
scalaxstream

Option converter for XStream in Scala


I'm trying to convert objects of type Option[T] to XML using Xstream in Scala. I have a case class like this:

case class MyModel(promos: Option[Promos])

If the option is Some(Promo), I'd like it to render

<MyModel>
  <Promos>
    <promoField1>value</promoField1>
    <promoField2>value</promoField2>
  </Promos>   
</MyModel>

If the option is None, I'd like it to render

<MyModel>
  <Promos/>
</MyModel>

So far in my solution, I have registered a converter:

xstream.registerConverter(new OptionConverter(xstream.getMapper)) 

I then have a custom converter that looks like this:

private [xml] class OptionConverter(_mapper: Mapper) extends AbstractCollectionConverter(_mapper: Mapper) {

  override def marshal(source: scala.Any, writer: HierarchicalStreamWriter, context: MarshallingContext): Unit = {
    val opt = source.asInstanceOf[Option[_]]
    for (value <- opt) {
      writeItem(value, context, writer)
    }
  }

  override def unmarshal(reader: HierarchicalStreamReader, context: UnmarshallingContext): AnyRef = {
    throw new UnsupportedOperationException
  }

  override def canConvert(clazz: Class[_]): Boolean = {
    clazz.isAssignableFrom(classOf[Some[_]]) || clazz.isAssignableFrom(None.getClass)
  }
}

The None works fine, but Some(promo) outputs like this:

<Promos>
  <com.mymodel.Promos>
    <promoField1>value</promoField1>
    <promoField2>value</promoField2>
  </com.mymodel.Promos>
</Promos>

The problem is, Promos is being output for my Option/Some field, "com.mymodel.Promos" is then being output for the nested value within Some. Is there a way to flatten is for Some(value)?


Solution

  • I managed to make the Option Converter to marshal objects to XML. (The reading part is not required for me, so I left that unimplemented)

    import com.thoughtworks.xstream.converters.{MarshallingContext, UnmarshallingContext}
    import com.thoughtworks.xstream.io.{HierarchicalStreamReader, HierarchicalStreamWriter}
    
    private [xml] sealed class OptionConverter extends com.thoughtworks.xstream.converters.Converter {
    
      override def marshal(source: scala.Any, writer: HierarchicalStreamWriter, context: MarshallingContext): Unit = {
        val opt = source.asInstanceOf[Option[_]]
        for (value <- opt) {
          context.convertAnother(opt.get)
        }
      }
    
      override def unmarshal(reader: HierarchicalStreamReader, context: UnmarshallingContext): AnyRef = {
        throw new UnsupportedOperationException
      }
    
      override def canConvert(clazz: Class[_]): Boolean = {
        clazz.isAssignableFrom(classOf[Some[_]]) || clazz.isAssignableFrom(None.getClass)
      }
    }