Dan Newcome, blog

I'm bringing cyber back

Release: Javascript XML serialization library

with 10 comments

I’m working on a project that will access MS CRM directly via Javascript. Microsoft uses SOAP web services as their API protocol, and arguments have to be in a very particular XML format. I couldn’t find anything off-the-shelf that would allow complex SOAP calls, so I started hacking. Part of the solution turned into a pretty flexible XML serializer for Javascript, so I’m open-sourcing that part of it now.

The source is on github here, and I’ve pasted the readme below.

About

js-xml-serializer is a Javascript serialization library that allows an arbitrary Javascript object to be serialized as XML according to a set of supplied rules and namespace definitions. The motivation behind this project was the author’s inability to find an existing Javascript serialization method that supported namespaces and allowed enough control to allow sending complex Javascript objects as arguments to a SOAP web service.

Usage

In the nominal case, given a Javascript object like the following:

var obj = {
    attr1: "two",
    attr2: new MyClass( "hello" ),
    attr3: [ "three", "four" ]
};

We can produce a simple XML document:

<Object>
    <String>two</String>
    <MyClass>
        <String>hello</String>
    </MyClass>
    <Array>
        <String>three</String>
        <String>four</String>
    </Array>
</Object>

Using the following code:

serialize( obj );

In order to enable more control over serialization, we can create a structure outlining specific serialization rules to be applied to the object:

var rules = {
    Object: {
        __def__: { nodetype: "element", nodename: "obj", namespace: "http://example.org/" },
        attr1: { nodetype: "element", nodename: "first", namespace: "http://example.org/" },
        attr2: { nodetype: "element", nodename: "myc-attr2", namespace: "http://example.org/" }
    },
    Array: {
        __def__: { nodetype: "element", nodename: "arr", namespace: "http://example.org/" }
    },
    String: {
        __def__: { nodetype: "element", nodename: "str", namespace: "http://example.org/" },
    },
    MyClass: {
        __def__: { nodetype: "element", nodename: "myc", namespace: "http://example.org/" }
    }
}

The rule denoted by __def__ will be applied to an appearance of the type in the object to be serialized if no other rule matches — that is, if no rule that applies to a particular member of another type overrides it.

Namespace prefixes are specified as the following:

var namespaces = {
    "http://example.org/": "ex",
}

The serialized XML output looks like this:

<ex:obj xmlns:ex='http://example.org/'>
    <ex:first>two</ex:first>
    <ex:myc-attr2>
        <ex:str>hello</ex:str>
    </ex:myc-attr2>
    <ex:arr>
        <ex:str>three</ex:str>
        <ex:str>four</ex:str>
    </ex:arr>
</ex:obj>

Status

js-xml-serializer is being developed as part of a larger project so it is only designed to solve serialization issues as-needed for that particular project. It is provided in the hopes that others may suggest extensions to cover cases that the author has not considered. This software is not being used by the author in production yet, and it is suggested that others test it fully before using it.

Future work

  • Clean up output: Output is not indented and some spurious (semantically insignificant) whitespace is emitted.
  • Provide default rules: Support for supplying a base rule set would make using the library more convenient. Provisions for merging rule sets could also prove useful.
  • Allow default namespace: Output explicitly defines a prefix for all namespaces. While semantically correct, it may produce more readable output to determine one namespace to use for the default.
  • Change def mechanism: the mechanism by which the default serialization rule is specified may not be the best solution. Comments are welcome for addressing this.
  • Don’t pollute namespace: No wrapper object is provided around the basic serialize() function.

License

js-xml-serializer is copyright 2010 Dan Newcome and is provided under the MIT free software license. See the file LICENSE for
the full text.

Advertisements

Written by newcome

August 11, 2010 at 4:34 pm

Posted in Uncategorized

10 Responses

Subscribe to comments with RSS.

  1. not working for me. errors on var retval = “\n”;

    tony cobb

    July 1, 2011 at 7:27 am

  2. Hi Tony, thanks for trying this project out. I pushed an update for IE compatibility just now. If you were using IE it probably wouldn’t have worked before this. If you show me the code you are trying to use it will help me support more cases. Paste code in the comments using:

    [sourcecode language="javascript"]
    ...
    [/sourcecode]

    newcome

    July 1, 2011 at 10:42 am

  3. function serialize_default( obj ) {
    	var retval = "<" + getName( obj ) + ">\n";
    	if( typeOf( obj ) == 'object' || typeOf( obj ) == 'array' ) {
    		for( var item in obj ) {
    			retval += serialize_default( obj[item] );	
    		}
    	}
    	else {
    		retval += obj + "\n";
    	}
    	retval += "</" + getName( obj ) + ">\n";
    	return retval;
    }
    

    it errors at the if statement. “SCRIPT5007: The value of the property ‘typeOf’ is null or undefined, not a Function object”

    my code is:

    function WriteQuery() {
        Query = new EntityQuery();
        Query.Entity = Entity;
        var xml = serialize(Query);
    }
    

    i’m writing a querybuilder, so this object is a little complex.
    here is the object: (i’m still a fairly new to coding so, be easy on me)

    
    function EntityQuery() {
        this.Entity = new QueriedEntity();
        this.DistinctResults = true;
    }
    
    function QueriedEntity() {
        this.Name = "";
        this.Alias = "";
        this.Properties = new PropertySet();
        this.Joins = new Array();
        this.OderBy = new Array();
        this.Filter = new Filter();
    }
    
    function Order() {
        this.Property = "";
        this.Type = "";
    }
    
    function Condition() {
        this.Property = "";
        this.Operator = "";
        this.Value = "";
    }
    
    function Filter() {
        this.Type = "";
        this.Items = new Array;
    }
    
    function Join() {
        this.EntityName = "";
        this.Joins = new Array();
        this.Conditions = new Array();
        this.Properties = new PropertySet();
        this.Type = "LEFT";
        this.Filters = new Array();
        this.Alias = "";
        this.IsInternalJoin = "";
        this.OrderBy = "";
    }
    
    function PropertySet() {
        this.Items = new Array();
    }
    
    function EntityProperty(propertyName) {
        this.Name = propertyName;
    }
    
    function PropAndHeader() {
        this.Name = "";
        this.Header = "";
    }
    
    function JoinCondition(from,to) {
        this.From = from;
        this.To = to;
    }
    
    

    tony cobb

    August 3, 2011 at 1:40 pm

  4. sorry, rushed to get the code out there and forgot the typeof. it works now, but seems to have problem adding attributes to elements.

    tony cobb

    August 3, 2011 at 1:51 pm

  5. ….well when i actually populate the object with a lot of data. i get this error.
    “SCRIPT5007: Unable to get value of the property ‘constructor’: object is null or undefined”

    function getName( obj ) {
    var funcNameRegex = /function (.{1,})\(/;
    var results = (funcNameRegex).exec((obj).constructor.toString());
    return (results && results.length > 1) ? results[1] : "";
    };

    one line 4, but maybe it’s something that i’m doing wrong.

    tony cobb

    August 3, 2011 at 2:05 pm

  6. The serializer didn’t handle null values gracefully. Probably that is what happened in your case. I’ve added a small change to the code to ignore null values in the input object graph.

    I thought about adding an option to emit an empty element or attribute when a null is encountered, but I don’t really need it, so I’d rather not add the complexity if I don’t have to.

    Try pulling the latest version and see if it helps.

    newcome

    August 4, 2011 at 10:11 am

  7. Hi ,

    I have a situation where i need to differentiate between a inner function & global function both having same name. In case of inner function being the type, how is it possible to define rules ?

    function book(){

    var name;
    var age;
    var part = [];

    function part(partid){
    this.partid = partid;
    };

    }

    usage will be as follows :

    var book_1 = new book();
    book_1.name=’Deepak’;
    book_1.age=’12’;
    var part1 = book_1.part(‘1’);
    var part2 = book_1.part(‘2’);
    book_1.part = [part1,part2];

    Deepak

    May 29, 2012 at 2:41 am

  8. Hi Deepak, I looked at what you are trying to do, and there are a few things that are confusing. You are not exposing name and age externally, and not using ‘new’ to use the inner part() function as a constructor. I wrote a test case using what I think you are trying to do. There should be no problem with having a type name and a property name that are the same, as the rules can differentiate them. See if the code below looks close to what you were trying to do. If not, post some updated code in a followup comment.

       var rules = {
                book: {
                        __def__: { nodetype: "element", nodename: "book", namespace: "http://example.org/" },
                        name: { nodetype: "element", nodename: "name", namespace: "http://example.org/" },
                        age: { nodetype: "element", nodename: "age", namespace: "http://example.org/" }
                },
                part: {
                        __def__: { nodetype: "element", nodename: "part", namespace: "http://example.org/" },
                        partid: { nodetype: "element", nodename: "partid", namespace: "http://example.org/" }
                },
                String: {
                        __def__: { nodetype: "element", nodename: "String", namespace: "http://example.org/" }
                },
                Array: {
                        __def__: { nodetype: "element", nodename: "Array", namespace: "http://example.org/" }
                } 
        }
    
        var namespaces = {
            "http://example.org/": "ex"
        }
    
        function part( partid ) {
            this.partid = partid;
        }
    
        function book() {
            var name;
            var age;
            var part = [];
        }
    
        var book_1 = new book();
        book_1.name = 'Deepak';
        book_1.age = '12';
        var part1 = new part( '1' );
        var part2 = new part( '2' );
        book_1.part = [ part1, part2 ];
    
        console.log( serialize( null, null, book_1, rules, namespaces ) );
    
    

    newcome

    May 29, 2012 at 9:57 pm

  9. I read this paragraph fully on the topic of the comparison of most recent and preceding technologies, it’s amazing article.

    optimum stack

    June 14, 2013 at 9:11 pm

  10. Hi there, I enjoy reading all of your post.
    I wanted to write a little comment to support you.

    Gordon

    August 6, 2013 at 6:21 pm


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: