views:

101

answers:

2

I know how to serialize in F# using mutable objects, but is there a way to serialize/deserialize using record types using either XmlSerializer or the DataContractSerializer? looks like there is a way to do this for a discriminated union using the KnownType attribute, but i am looking for a way to use non-mutable records without default constructor...

+1  A: 

The sample code for reading data from Freebase by Jomo Fisher uses DataContractJsonSerializer to load data into immutable F# records. The declaration of the record that he uses looks like this:

[<DataContract>]
type Result<'TResult> = { // '
    [<field: DataMember(Name="code") >]
    Code:string
    [<field: DataMember(Name="result") >]
    Result:'TResult // '
    [<field: DataMember(Name="message") >]
    Message:string }

The key point here is that the the DataMember attribute is attached to the underlying field that's actually used to store the data and not to the read-only property that the F# compiler generates (using the field: modifier on the attribute).

I'm not 100% sure if this is going to work with other types of serialization (probably not), but it may be a useful pointer to start with...

EDIT I'm not sure if I'm missing something here, but the following basic example works fine for me:

module Demo

open System.IO  
open System.Text  
open System.Xml 
open System.Runtime.Serialization

type Test = 
  { Result : string[]
    Title : string }

do
  let sb = new StringBuilder()
  let value = { Result = [| "Hello"; "World" |]; Title = "Hacking" }
  let xmlSerializer = DataContractSerializer(typeof<Test>); 
  xmlSerializer.WriteObject(new XmlTextWriter(new StringWriter(sb)), value)
  let sr = sb.ToString()
  printfn "%A" sr

  let xmlSerializer = DataContractSerializer(typeof<Test>); 
  let reader = new XmlTextReader(new StringReader(sr))
  let obj = xmlSerializer.ReadObject(reader) :?> Test
  printfn "Reading: %A" obj

EDIT 2 If you want to generate cleaner XML then you can add attributes like this:

[<XmlRoot("test")>] 
type Test = 
  { [<XmlArrayAttribute("results")>] 
    [<XmlArrayItem(typeof<string>, ElementName = "string")>] 
    Result : string[]
    [<XmlArrayAttribute("title")>] 
    Title : string }
Tomas Petricek
this is a start, but the xml that's being generated is not "clean", i mean the tags are like "FSI_0132.Test" instead of Test and Title_x0040_ instead of title . i also looks like cannot set the xml tags on the type with attributes like [<XmlElement "foo" >] . in other words, the xml looks intermediate, not something that i could really use. btw, Tomas, i am starting to read your Real-World Functional Programming book, it looks really good so far.
Alex
The namespace "FSI_0132" is generated by F# Interactive - in compiled code, it would be replaced with your actual namespace. Not sure about the property name, though.
Joel Mueller
@Joel: right, i understand that, but my goal is to have clean xml without any namespace information, like you would if you tried to deserialize using XmlSerializer class in .net
Alex
@Alex: I tried adding attributes to control the generated XML and it works fine too - see the second edit. (this time the attributes need to be added to the generated properties though)
Tomas Petricek
Tomas, i am not 100% sure why, but my output looks like this:<Program.Test xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/"> <Result_x0040_ xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays"> <d2p1:string>Hello</d2p1:string> <d2p1:string>World</d2p1:string> </Result_x0040_> <Title_x0040_>Hacking</Title_x0040_></Program.Test> notice, that the attributes seem to be ignored
Alex
A: 

You can use this series of annotations on the properties of classes to format the XML:

[XmlRoot("root")]
[XmlElement("some-element")]
[XmlAttribute("some-attribute")]
[XmlArrayAttribute("collections")]
[XmlArrayItem(typeof(SomeClass), ElementName = "item")]

I use the attributes on my c# classes, but deserialize in F# (c# classes are ina referenced lib).

in f#:

use file = new FileStream(filePath, FileMode.Open)
let serializer= XmlSerializer(typeof<SomeClass>)
let docs = serializer.Deserialize file :?> SomeClass
akaphenom
that's cool, but the requirement is to use non-mutable types, so this approach will not work since the class will have mutable fields
Alex