Question

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)
Was it helpful?

Solution 3

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)

OTHER TIPS

Blockquote I was hoping there could be some shortcut for getting a parameterless constructor onto a record

This is possible in F# version 3.0 via use of the CLIMutable attribute. Add this to your record type for a parameterless constructor and read/write properties (instead of read only properties).

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.)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top