.netf#datacontractserializerxmlserializer

F# Add Constructor to a Record?


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)

Solution

  • 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: Link) 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)