views:

174

answers:

2

Basically I want to have a single construct to deal with serializing to both JSON and formatted xml. Records worked nicely for serializing to/from json. However XmlSerializer requires a parameterless construtor. I don't really want to have to go through the exercise of building class objects for these constructs (principle only). I was hoping there could be some shortcut for getting a parameterless constructor onto a record (perhaps with a wioth statement or something). I can't get it to behave - has anybody in the community had any luck?

module JSONExample
    open System
    open System.IO 
    open System.Net 
    open System.Text 
    open System.Web 
    open System.Xml
    open System.Security.Authentication 
    open System.Runtime.Serialization //add assemnbly reference System.Runtime.Serialization System.Xml
    open System.Xml.Serialization
    open System.Collections.Generic 

    [<DataContract>]            
    type ChemicalElementRecord = { 
        [<XmlAttribute("name")>]
        [<field: DataMember(Name="name") >] 
        Name:string 

        [<XmlAttribute("name")>]
        [<field: DataMember(Name="boiling_point") >] 
        BoilingPoint:string 

        [<XmlAttribute("atomic-mass")>]
        [<field: DataMember(Name="atomic_mass") >] 
        AtomicMass:string 
    } 

    [<XmlRoot("freebase")>]
    [<DataContract>] 
    type FreebaseResultRecord = { 
        [<XmlAttribute("code")>]
        [<field: DataMember(Name="code") >] 
        Code:string 

        [<XmlArrayAttribute("results")>]
        [<XmlArrayItem(typeof<ChemicalElementRecord>, ElementName = "chemical-element")>]
        [<field: DataMember(Name="result") >] 
        Result: ChemicalElementRecord array

        [<XmlElement("message")>] 
        [<field: DataMember(Name="message") >] 
        Message:string 
        } 


    let getJsonFromWeb() = 
        let query = "[{'type':'/chemistry/chemical_element','name':null,'boiling_point':null,'atomic_mass':null}]"
        let query = query.Replace("'","\"") 
        let queryUrl = sprintf "http://api.freebase.com/api/service/mqlread?query=%s" "{\"query\":"+query+"}" 

        let request : HttpWebRequest = downcast WebRequest.Create(queryUrl) 
        request.Method <- "GET" 
        request.ContentType <- "application/x-www-form-urlencoded" 

        let response = request.GetResponse() 

        let result = 
            try 
                use reader = new StreamReader(response.GetResponseStream()) 
                reader.ReadToEnd(); 
            finally 
                response.Close() 

        let data = Encoding.Unicode.GetBytes(result); 
        let stream = new MemoryStream() 
        stream.Write(data, 0, data.Length); 
        stream.Position <- 0L 
        stream



    let test =
        // get some JSON from the web
        let stream = getJsonFromWeb()

        // convert the stream of JSON into an F# Record
        let JsonSerializer = Json.DataContractJsonSerializer(typeof<FreebaseResultRecord>) 
        let result: FreebaseResultRecord = downcast JsonSerializer.ReadObject(stream) 

        // save the Records to disk as JSON 
        use fs = new FileStream(@"C:\temp\freebase.json", FileMode.Create) 
        JsonSerializer.WriteObject(fs,result)
        fs.Close()

        // save the Records to disk as System Controlled XML
        let xmlSerializer = DataContractSerializer(typeof<FreebaseResultRecord>);
        use fs = new FileStream(@"C:\temp\freebase.xml", FileMode.Create) 
        xmlSerializer.WriteObject(fs,result)
        fs.Close()

        use fs = new FileStream(@"C:\temp\freebase-pretty.xml", FileMode.Create) 
        let xmlSerializer = XmlSerializer(typeof<FreebaseResultRecord>)
        xmlSerializer.Serialize(fs,result)
        fs.Close()

ignore(test)
+1  A: 

Why not use the DataContractSerializer (for Xml) rather than XmlSerializer? That's one of the main benefits of data contracts (same programming model for multiple projections).

(There is no way to make a parameterless constructor for an F# record.)

Brian
Can you control the output format of the XML for the DataContractSerializer? I tried originally and it wasn't comming out too nicley. As you can see I have three serializaitons to disk and the secnod one uses that format.
akaphenom
I guess the real issue is the addition of the Namespaces and the i.Nil attributes...
akaphenom
I see. A quick web search suggests no; see e.g. http://stackoverflow.com/questions/1953984/is-there-a-way-to-make-datacontractserializer-output-cleaner-xml
Brian
You actually have a tad more control than that post, by adding Name properties to the attributes. But it is not perfect. Really if there was a cheap way of Converting f# records to classes - it woudl be ideal and then I could XmlSerialize them...
akaphenom
Yeah, sigh, I never use records, as once you need 'a little more' from them it's a bit of hassle to change from a record to a class.
Brian
A: 

Looks like you can't change a record to a class - or add a edfault constructor to it.

The example provided (basedof off this article: http://blogs.msdn.com/b/jomo_fisher/archive/2010/03/06/neat-sample-f-and-freebase.aspx) gets a stream of json from api.freebase.com; we then deserialize into the attributed classes; next serialize it as Json to disk; then serialize it as Xml to disk (using DataContract); finally, with the best controll of the output serialize it as Xml to disk (using XmlSerializer):

Notes:

DataContract(family of) attributes for DataContractJsonSerializer and JSON.DataContractJsonSerializer - these occur over the class names and the memeber variables. DataContract stuff was straight forward - and works on record types as well.

XmlSerializer(family of) attributes over the class and the Property Getter/Setter. This requires the type is an object with a default constructor, and property Getters and Setters w/ attributes associated with each of them. If a Property doesn't have eitehr a getter or a setter it will not serialize - which was a suprise (I imagined the default onstructor would ensure that the object had defulat values upon deserialization and the setters would update with whatever was serialized - but no this isn't the case).

Another nifty (sigh) thing about XmlSerialization is that the classes can't be contained within a module. So we move the types up to a namespace...

namespace JSONExample
    open System
    open System.IO 
    open System.Net 
    open System.Text 
    open System.Web 
    open System.Xml
    open System.Security.Authentication 
    open System.Runtime.Serialization //add assemnbly reference System.Runtime.Serialization System.Xml
    open System.Xml.Serialization
    open System.Collections.Generic 

    [<DataContract>]            
    type ChemicalElementRecord() =  
        [<field: DataMember(Name="name") >] 
        let mutable name: string  = ""

        [<field: DataMember(Name="boiling_point") >] 
        let mutable boilingPoint: string =""

        [<field: DataMember(Name="atomic_mass") >] 
        let mutable atomicMass: string  = ""

        [<XmlAttribute("name")>]
        member this.Name with get() = name and set v = name <- v

        [<XmlAttribute("boiling-point")>]
        member this.BoilingPoint with get()  = boilingPoint  and set v = boilingPoint <- v

        [<XmlAttribute("atomic-mass")>]
        member this.AtomicMass with get() = atomicMass  and set v = atomicMass <- v

    [<XmlRoot("freebase")>]
    [<DataContract>] 
    type FreebaseResultRecord() =  

        [<field: DataMember(Name="code") >] 
        let mutable code: string = ""

        [<field: DataMember(Name="result") >] 
        let mutable result: ChemicalElementRecord array = Array.empty

        [<field: DataMember(Name="message") >] 
        let mutable message: string = ""

        [<XmlElement("message")>] 
        member this.Message with get() : string = message and set v = message <- v

        [<XmlArrayAttribute("chemical-elements")>]
        [<XmlArrayItem(typeof<ChemicalElementRecord>, ElementName = "chemical-element")>]
        member this.Result with get() = result and set v = result <- v

        [<XmlAttribute("code")>]
        member this.Code with get() = code and set v = code <- v

    module Test = 
        let getJsonFromWeb() = 
            let query = "[{'type':'/chemistry/chemical_element','name':null,'boiling_point':null,'atomic_mass':null}]"
            let query = query.Replace("'","\"") 
            let queryUrl = sprintf "http://api.freebase.com/api/service/mqlread?query=%s" "{\"query\":"+query+"}" 

            let request : HttpWebRequest = downcast WebRequest.Create(queryUrl) 
            request.Method <- "GET" 
            request.ContentType <- "application/x-www-form-urlencoded" 

            let response = request.GetResponse() 

            let result = 
                try 
                    use reader = new StreamReader(response.GetResponseStream()) 
                    reader.ReadToEnd(); 
                finally 
                    response.Close() 

            let data = Encoding.Unicode.GetBytes(result); 
            let stream = new MemoryStream() 
            stream.Write(data, 0, data.Length); 
            stream.Position <- 0L 
            stream



        let test =
            // get some JSON from the web
            let stream = getJsonFromWeb()

            // convert the stream of JSON into an F# Record
            let JsonSerializer = Json.DataContractJsonSerializer(typeof<FreebaseResultRecord>) 
            let result: FreebaseResultRecord = downcast JsonSerializer.ReadObject(stream) 

            // save the Records to disk as JSON 
            use fs = new FileStream(@"C:\temp\freebase.json", FileMode.Create) 
            JsonSerializer.WriteObject(fs,result)
            fs.Close()

            // save the Records to disk as System Controlled XML
            let xmlSerializer = DataContractSerializer(typeof<FreebaseResultRecord>);
            use fs = new FileStream(@"C:\temp\freebase.xml", FileMode.Create) 
            xmlSerializer.WriteObject(fs,result)
            fs.Close()

            use fs = new FileStream(@"C:\temp\freebase-pretty.xml", FileMode.Create) 
            let xmlSerializer = XmlSerializer(typeof<FreebaseResultRecord>)
            xmlSerializer.Serialize(fs,result)
            fs.Close()


        ignore(test)
akaphenom