You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@thrift.apache.org by he...@apache.org on 2015/06/25 14:33:21 UTC

thrift git commit: THRIFT-3122 Javascript struct constructor should properly initialize struct and container members from plain js arguments Patch: Igor Tkach

Repository: thrift
Updated Branches:
  refs/heads/master 0b8132d20 -> 15d904240


THRIFT-3122 Javascript struct constructor should properly initialize struct and container members from plain js arguments
Patch:  Igor Tkach

This closes #519


Project: http://git-wip-us.apache.org/repos/asf/thrift/repo
Commit: http://git-wip-us.apache.org/repos/asf/thrift/commit/15d90424
Tree: http://git-wip-us.apache.org/repos/asf/thrift/tree/15d90424
Diff: http://git-wip-us.apache.org/repos/asf/thrift/diff/15d90424

Branch: refs/heads/master
Commit: 15d904240e8ee446ce5b9549c1082ea078389774
Parents: 0b8132d
Author: Henrique Mendonça <he...@apache.org>
Authored: Thu Jun 25 22:31:41 2015 +1000
Committer: Henrique Mendonça <he...@apache.org>
Committed: Thu Jun 25 22:31:41 2015 +1000

----------------------------------------------------------------------
 compiler/cpp/src/generate/t_js_generator.cc |  61 +++++-
 lib/js/Gruntfile.js                         |  21 +-
 lib/js/src/thrift.js                        |  68 ++++++-
 lib/js/test/deep-constructor.test.js        | 195 +++++++++++++++++++
 lib/js/test/test-deep-constructor.html      |  49 +++++
 lib/nodejs/lib/thrift/json_protocol.js      |  10 +-
 lib/nodejs/lib/thrift/thrift.js             |  67 +++++++
 lib/nodejs/test/deep-constructor.test.js    | 233 +++++++++++++++++++++++
 lib/nodejs/test/test-cases.js               |  17 ++
 lib/nodejs/test/testAll.sh                  |   2 +
 lib/nodejs/test/test_driver.js              |  11 ++
 package.json                                |   1 +
 test/JsDeepConstructorTest.thrift           |  12 ++
 13 files changed, 737 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/thrift/blob/15d90424/compiler/cpp/src/generate/t_js_generator.cc
----------------------------------------------------------------------
diff --git a/compiler/cpp/src/generate/t_js_generator.cc b/compiler/cpp/src/generate/t_js_generator.cc
index 970a179..2c90e4c 100644
--- a/compiler/cpp/src/generate/t_js_generator.cc
+++ b/compiler/cpp/src/generate/t_js_generator.cc
@@ -41,6 +41,7 @@ static const string endl = "\n"; // avoid ostream << std::endl flushes
 
 #include "t_oop_generator.h"
 
+
 /**
  * JS code generator.
  */
@@ -181,6 +182,8 @@ public:
            + "//\n";
   }
 
+  t_type* get_contained_type(t_type* t);
+
   std::vector<std::string> js_namespace_pieces(t_program* p) {
     std::string ns = p->get_namespace("js");
 
@@ -601,6 +604,22 @@ void t_js_generator::generate_js_struct(t_struct* tstruct, bool is_exception) {
 }
 
 /**
+ * Return type of contained elements for a container type. For maps
+ * this is type of value (keys are always strings in js)
+ */
+t_type* t_js_generator::get_contained_type(t_type* t) {
+  t_type* etype;
+  if (t->is_list()) {
+    etype = ((t_list*)t)->get_elem_type();
+  } else if (t->is_set()) {
+    etype = ((t_set*)t)->get_elem_type();
+  } else {
+    etype = ((t_map*)t)->get_val_type();
+  }
+  return etype;
+}
+
+/**
  * Generates a struct definition for a thrift data type. This is nothing in JS
  * where the objects are all just associative arrays (unless of course we
  * decide to start using objects for them...)
@@ -685,9 +704,47 @@ void t_js_generator::generate_js_struct_definition(ofstream& out,
     }
 
     for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) {
+      t_type* t = get_true_type((*m_iter)->get_type());
       out << indent() << indent() << "if (args." << (*m_iter)->get_name() << " !== undefined) {"
-          << endl << indent() << indent() << indent() << "this." << (*m_iter)->get_name()
-          << " = args." << (*m_iter)->get_name() << ";" << endl;
+          << endl << indent() << indent() << indent() << "this." << (*m_iter)->get_name();
+
+      if (t->is_struct()) {
+        out << (" = new " + js_type_namespace(t->get_program()) + t->get_name() +
+                "(args."+(*m_iter)->get_name() +");");
+        out << endl;
+      } else if (t->is_container()) {
+        t_type* etype = get_contained_type(t);
+        string copyFunc = t->is_map() ? "Thrift.copyMap" : "Thrift.copyList";
+        string type_list = "";
+
+        while (etype->is_container()) {
+          if (type_list.length() > 0) {
+            type_list += ", ";
+          }
+          type_list += etype->is_map() ? "Thrift.copyMap" : "Thrift.copyList";
+          etype = get_contained_type(etype);
+        }
+
+        if (etype->is_struct()) {
+          if (type_list.length() > 0) {
+            type_list += ", ";
+          }
+          type_list += js_type_namespace(etype->get_program()) + etype->get_name();
+        }
+        else {
+          if (type_list.length() > 0) {
+            type_list += ", ";
+          }
+          type_list += "null";
+        }
+
+        out << (" = " + copyFunc + "(args." + (*m_iter)->get_name() +
+                ", [" + type_list + "]);");
+        out << endl;
+      } else {
+        out << " = args." << (*m_iter)->get_name() << ";" << endl;
+      }
+
       if (!(*m_iter)->get_req()) {
         out << indent() << indent() << "} else {" << endl << indent() << indent() << indent()
             << "throw new Thrift.TProtocolException(Thrift.TProtocolExceptionType.UNKNOWN, "

http://git-wip-us.apache.org/repos/asf/thrift/blob/15d90424/lib/js/Gruntfile.js
----------------------------------------------------------------------
diff --git a/lib/js/Gruntfile.js b/lib/js/Gruntfile.js
index 3298d5c..32c8834 100644
--- a/lib/js/Gruntfile.js
+++ b/lib/js/Gruntfile.js
@@ -35,7 +35,7 @@ module.exports = function(grunt) {
           'dist/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>']
         }
       }
-    },  
+    },
     shell: {
       InstallThriftJS: {
         command: 'mkdir test/build; mkdir test/build/js; cp src/thrift.js test/build/js/thrift.js'
@@ -48,6 +48,9 @@ module.exports = function(grunt) {
       },
       ThriftGenJQ: {
         command: 'thrift -gen js:jquery -gen js:node -o test ../../test/ThriftTest.thrift'
+      },
+      ThriftGenDeepConstructor: {
+        command: 'thrift -gen js -o test ../../test/JsDeepConstructorTest.thrift'
       }
     },
     external_daemon: {
@@ -123,6 +126,13 @@ module.exports = function(grunt) {
             'https://localhost:8089/testws.html'
           ]
         }
+      },
+      ThriftDeepConstructor: {
+        options: {
+          urls: [
+            'http://localhost:8088/test-deep-constructor.html'
+          ]
+        }
       }
     },
     jshint: {
@@ -147,13 +157,14 @@ module.exports = function(grunt) {
   grunt.loadNpmTasks('grunt-external-daemon');
   grunt.loadNpmTasks('grunt-shell');
 
-  grunt.registerTask('test', ['jshint', 'shell:InstallThriftJS', 'shell:InstallThriftNodeJSDep', 'shell:ThriftGen', 
-                              'external_daemon:ThriftTestServer', 'external_daemon:ThriftTestServer_TLS', 
+  grunt.registerTask('test', ['jshint', 'shell:InstallThriftJS', 'shell:InstallThriftNodeJSDep', 'shell:ThriftGen',
+                              'external_daemon:ThriftTestServer', 'external_daemon:ThriftTestServer_TLS',
+                              'shell:ThriftGenDeepConstructor', 'qunit:ThriftDeepConstructor',
                               'qunit:ThriftJS', 'qunit:ThriftJS_TLS',
                               'shell:ThriftGenJQ', 'qunit:ThriftJSJQ', 'qunit:ThriftJSJQ_TLS'
                              ]);
-  grunt.registerTask('default', ['jshint', 'shell:InstallThriftJS', 'shell:InstallThriftNodeJSDep', 'shell:ThriftGen', 
-                                 'external_daemon:ThriftTestServer', 'external_daemon:ThriftTestServer_TLS', 
+  grunt.registerTask('default', ['jshint', 'shell:InstallThriftJS', 'shell:InstallThriftNodeJSDep', 'shell:ThriftGen',
+                                 'external_daemon:ThriftTestServer', 'external_daemon:ThriftTestServer_TLS',
                                  'qunit:ThriftJS', 'qunit:ThriftJS_TLS',
                                  'shell:ThriftGenJQ', 'qunit:ThriftJSJQ', 'qunit:ThriftJSJQ_TLS',
                                  'concat', 'uglify', 'jsdoc'

http://git-wip-us.apache.org/repos/asf/thrift/blob/15d90424/lib/js/src/thrift.js
----------------------------------------------------------------------
diff --git a/lib/js/src/thrift.js b/lib/js/src/thrift.js
index af45d9c..9bd1198 100644
--- a/lib/js/src/thrift.js
+++ b/lib/js/src/thrift.js
@@ -1203,7 +1203,7 @@ Thrift.Protocol.prototype = {
         r.size = list.shift();
 
         this.rpos.push(this.rstack.length);
-        this.rstack.push(list);
+        this.rstack.push(list.shift());
 
         return r;
     },
@@ -1439,3 +1439,69 @@ Thrift.Multiplexer.prototype.createClient = function (serviceName, SCl, transpor
 
 
 
+var copyList, copyMap;
+
+copyList = function(lst, types) {
+
+  if (!lst) {return lst; }
+
+  var type;
+
+  if (types.shift === undefined) {
+    type = types;
+  }
+  else {
+    type = types[0];
+  }
+  var Type = type;
+
+  var len = lst.length, result = [], i, val;
+  for (i = 0; i < len; i++) {
+    val = lst[i];
+    if (type === null) {
+      result.push(val);
+    }
+    else if (type === copyMap || type === copyList) {
+      result.push(type(val, types.slice(1)));
+    }
+    else {
+      result.push(new Type(val));
+    }
+  }
+  return result;
+};
+
+copyMap = function(obj, types){
+
+  if (!obj) {return obj; }
+
+  var type;
+
+  if (types.shift === undefined) {
+    type = types;
+  }
+  else {
+    type = types[0];
+  }
+  var Type = type;
+
+  var result = {}, val;
+  for(var prop in obj) {
+    if(obj.hasOwnProperty(prop)) {
+      val = obj[prop];
+      if (type === null) {
+        result[prop] = val;
+      }
+      else if (type === copyMap || type === copyList) {
+        result[prop] = type(val, types.slice(1));
+      }
+      else {
+        result[prop] = new Type(val);
+      }
+    }
+  }
+  return result;
+};
+
+Thrift.copyMap = copyMap;
+Thrift.copyList = copyList;

http://git-wip-us.apache.org/repos/asf/thrift/blob/15d90424/lib/js/test/deep-constructor.test.js
----------------------------------------------------------------------
diff --git a/lib/js/test/deep-constructor.test.js b/lib/js/test/deep-constructor.test.js
new file mode 100644
index 0000000..9a19809
--- /dev/null
+++ b/lib/js/test/deep-constructor.test.js
@@ -0,0 +1,195 @@
+function serialize(data) {
+  var transport = new Thrift.Transport("/service");
+  var protocol  = new Thrift.Protocol(transport);
+  protocol.writeMessageBegin("", 0, 0);
+  data.write(protocol);
+  protocol.writeMessageEnd();
+  return transport.send_buf;
+}
+
+function deserialize(serialized, type) {
+  var transport = new Thrift.Transport("/service");
+  transport.setRecvBuffer(serialized);
+  var protocol  = new Thrift.Protocol(transport);
+  protocol.readMessageBegin();
+  var data = new type();
+  data.read(protocol);
+  protocol.readMessageEnd();
+  return data;
+}
+
+
+function createThriftObj() {
+
+  return new Complex({
+
+    struct_field: new Simple({value: 'a'}),
+
+    struct_list_field: [
+      new Simple({value: 'b'}),
+      new Simple({value: 'c'}),
+    ],
+
+    struct_set_field: [
+      new Simple({value: 'd'}),
+      new Simple({value: 'e'}),
+    ],
+
+    struct_map_field: {
+      A: new Simple({value: 'f'}),
+      B: new Simple({value: 'g'})
+    },
+
+    struct_nested_containers_field: [
+      [
+        {
+          C: [
+            new Simple({value: 'h'}),
+            new Simple({value: 'i'})
+          ]
+        }
+      ]
+    ],
+
+
+    struct_nested_containers_field2: {
+      D: [
+        {
+          DA: new Simple({value: 'j'})
+        },
+        {
+          DB: new Simple({value: 'k'})
+        }
+      ]
+    }
+  }
+  );
+}
+
+
+function createJsObj() {
+
+  return {
+
+    struct_field: {value: 'a'},
+
+    struct_list_field: [
+      {value: 'b'},
+      {value: 'c'},
+    ],
+
+    struct_set_field: [
+      {value: 'd'},
+      {value: 'e'},
+    ],
+
+    struct_map_field: {
+      A: {value: 'f'},
+      B: {value: 'g'}
+    },
+
+    struct_nested_containers_field: [
+      [
+        {
+          C: [
+            {value: 'h'},
+            {value: 'i'}
+          ]
+        }
+      ]
+    ],
+
+    struct_nested_containers_field2: {
+      D: [
+        {
+          DA: {value: 'j'}
+        },
+        {
+          DB: {value: 'k'}
+        }
+      ]
+    }
+  };
+}
+
+
+function assertValues(obj, assert) {
+    assert.equal(obj.struct_field.value, 'a');
+    assert.equal(obj.struct_list_field[0].value, 'b');
+    assert.equal(obj.struct_list_field[1].value, 'c');
+    assert.equal(obj.struct_set_field[0].value, 'd');
+    assert.equal(obj.struct_set_field[1].value, 'e');
+    assert.equal(obj.struct_map_field.A.value, 'f');
+    assert.equal(obj.struct_map_field.B.value, 'g');
+    assert.equal(obj.struct_nested_containers_field[0][0].C[0].value, 'h');
+    assert.equal(obj.struct_nested_containers_field[0][0].C[1].value, 'i');
+    assert.equal(obj.struct_nested_containers_field2.D[0].DA.value, 'j');
+    assert.equal(obj.struct_nested_containers_field2.D[1].DB.value, 'k');
+}
+
+var cases = {
+
+  "Serialize/deserialize simple struct should return equal object": function(assert){
+    var tObj = new Simple({value: 'a'});
+    var received = deserialize(serialize(tObj), Simple);
+    assert.ok(tObj !== received);
+    assert.deepEqual(received, tObj);
+  },
+
+
+  "Serialize/deserialize should return equal object": function(assert){
+    var tObj = createThriftObj();
+    var received = deserialize(serialize(tObj), Complex);
+    assert.ok(tObj !== received);
+    assert.deepEqual(received, tObj);
+  },
+
+  "Nested structs and containers initialized from plain js objects should serialize same as if initialized from thrift objects": function(assert) {
+    var tObj1 = createThriftObj();
+    var tObj2 = new Complex(createJsObj());
+    assertValues(tObj2, assert);
+    assert.equal(serialize(tObj2), serialize(tObj1));
+  },
+
+  "Modifications to args object should not affect constructed Thrift object": function (assert) {
+
+    var args = createJsObj();
+    assertValues(args, assert);
+
+    var tObj = new Complex(args);
+    assertValues(tObj, assert);
+
+    args.struct_field.value = 'ZZZ';
+    args.struct_list_field[0].value = 'ZZZ';
+    args.struct_list_field[1].value = 'ZZZ';
+    args.struct_set_field[0].value = 'ZZZ';
+    args.struct_set_field[1].value = 'ZZZ';
+    args.struct_map_field.A.value = 'ZZZ';
+    args.struct_map_field.B.value = 'ZZZ';
+    args.struct_nested_containers_field[0][0].C[0] = 'ZZZ';
+    args.struct_nested_containers_field[0][0].C[1] = 'ZZZ';
+    args.struct_nested_containers_field2.D[0].DA = 'ZZZ';
+    args.struct_nested_containers_field2.D[0].DB = 'ZZZ';
+
+    assertValues(tObj, assert);
+  },
+
+  "nulls are ok": function(assert) {
+    var tObj = new Complex({
+      struct_field: null,
+      struct_list_field: null,
+      struct_set_field: null,
+      struct_map_field: null,
+      struct_nested_containers_field: null,
+      struct_nested_containers_field2: null
+    });
+    var received = deserialize(serialize(tObj), Complex);
+    assert.ok(tObj !== received);
+    assert.deepEqual(tObj, received);
+  }
+
+};
+
+Object.keys(cases).forEach(function(caseName) {
+  test(caseName, cases[caseName]);
+});

http://git-wip-us.apache.org/repos/asf/thrift/blob/15d90424/lib/js/test/test-deep-constructor.html
----------------------------------------------------------------------
diff --git a/lib/js/test/test-deep-constructor.html b/lib/js/test/test-deep-constructor.html
new file mode 100755
index 0000000..5835dc8
--- /dev/null
+++ b/lib/js/test/test-deep-constructor.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements. See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership. The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License. You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied. See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+  <title>Thrift Javascript Bindings: Unit Test</title>
+
+  <script src="build/js/thrift.js"         type="text/javascript" charset="utf-8"></script>
+  <script src="gen-js/JsDeepConstructorTest_types.js" type="text/javascript" charset="utf-8"></script>
+  <!-- jQuery -->
+  <script type="text/javascript" src="http://code.jquery.com/jquery-1.7.2.js" charset="utf-8"></script>
+
+  <!-- QUnit Test framework-->
+  <script type="text/javascript" src="http://code.jquery.com/qunit/qunit-1.14.0.js" charset="utf-8"></script>
+  <link rel="stylesheet" href="http://code.jquery.com/qunit/qunit-1.14.0.css" type="text/css" media="screen" />
+
+  <!-- the Test Suite-->
+  <script type="text/javascript" src="deep-constructor.test.js" charset="utf-8"></script>
+</head>
+<body>
+  <h1 id="qunit-header">Thrift Javascript Bindings: Deep Constructor Test (<a href="https://git-wip-us.apache.org/repos/asf?p=thrift.git;a=blob;f=test/JsDeepConstructorTest.thrift;hb=HEAD">JsDeepConstructorTest.thrift</a>)</h1>
+  <h2 id="qunit-banner"></h2>
+  <div id="qunit-testrunner-toolbar"></div>
+  <h2 id="qunit-userAgent"></h2>
+  <ol id="qunit-tests"><li><!-- get valid xhtml strict--></li></ol>
+  <p>
+      <a href="http://validator.w3.org/check/referer"><img
+          src="http://www.w3.org/Icons/valid-xhtml10"
+          alt="Valid XHTML 1.0!" height="31" width="88" /></a>
+  </p>
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/thrift/blob/15d90424/lib/nodejs/lib/thrift/json_protocol.js
----------------------------------------------------------------------
diff --git a/lib/nodejs/lib/thrift/json_protocol.js b/lib/nodejs/lib/thrift/json_protocol.js
index 77339f7..e98650c 100644
--- a/lib/nodejs/lib/thrift/json_protocol.js
+++ b/lib/nodejs/lib/thrift/json_protocol.js
@@ -546,9 +546,15 @@ TJSONProtocol.prototype.readFieldEnd = function() {
  */
 TJSONProtocol.prototype.readMapBegin = function() {
   var map = this.rstack.pop();
+  var first = map.shift();
+  if (first instanceof Array) {
+    this.rstack.push(map);
+    map = first;
+    first = map.shift();
+  }
 
   var r = {};
-  r.ktype = TJSONProtocol.RType[map.shift()];
+  r.ktype = TJSONProtocol.RType[first];
   r.vtype = TJSONProtocol.RType[map.shift()];
   r.size = map.shift();
 
@@ -582,7 +588,7 @@ TJSONProtocol.prototype.readListBegin = function() {
   r.size = list.shift();
 
   this.rpos.push(this.rstack.length);
-  this.rstack.push(list);
+  this.rstack.push(list.shift());
 
   return r;
 };

http://git-wip-us.apache.org/repos/asf/thrift/blob/15d90424/lib/nodejs/lib/thrift/thrift.js
----------------------------------------------------------------------
diff --git a/lib/nodejs/lib/thrift/thrift.js b/lib/nodejs/lib/thrift/thrift.js
index 89c789d..e6edf9e 100644
--- a/lib/nodejs/lib/thrift/thrift.js
+++ b/lib/nodejs/lib/thrift/thrift.js
@@ -151,3 +151,70 @@ exports.objectLength = function(obj) {
 exports.inherits = function(constructor, superConstructor) {
   util.inherits(constructor, superConstructor);
 };
+
+var copyList, copyMap;
+
+copyList = function(lst, types) {
+
+  if (!lst) {return lst; }
+
+  var type;
+
+  if (types.shift === undefined) {
+    type = types;
+  }
+  else {
+    type = types[0];
+  }
+  var Type = type;
+
+  var len = lst.length, result = [], i, val;
+  for (i = 0; i < len; i++) {
+    val = lst[i];
+    if (type === null) {
+      result.push(val);
+    }
+    else if (type === copyMap || type === copyList) {
+      result.push(type(val, types.slice(1)));
+    }
+    else {
+      result.push(new Type(val));
+    }
+  }
+  return result;
+};
+
+copyMap = function(obj, types){
+
+  if (!obj) {return obj; }
+
+  var type;
+
+  if (types.shift === undefined) {
+    type = types;
+  }
+  else {
+    type = types[0];
+  }
+  var Type = type;
+
+  var result = {}, val;
+  for(var prop in obj) {
+    if(obj.hasOwnProperty(prop)) {
+      val = obj[prop];
+      if (type === null) {
+        result[prop] = val;
+      }
+      else if (type === copyMap || type === copyList) {
+        result[prop] = type(val, types.slice(1));
+      }
+      else {
+        result[prop] = new Type(val);
+      }
+    }
+  }
+  return result;
+};
+
+module.exports.copyMap = copyMap;
+module.exports.copyList = copyList;

http://git-wip-us.apache.org/repos/asf/thrift/blob/15d90424/lib/nodejs/test/deep-constructor.test.js
----------------------------------------------------------------------
diff --git a/lib/nodejs/test/deep-constructor.test.js b/lib/nodejs/test/deep-constructor.test.js
new file mode 100644
index 0000000..41e7dd0
--- /dev/null
+++ b/lib/nodejs/test/deep-constructor.test.js
@@ -0,0 +1,233 @@
+var ttypes = require('./gen-nodejs/JsDeepConstructorTest_types');
+var thrift = require('thrift');
+var test = require('tape');
+var bufferEquals = require('buffer-equals');
+
+function serializeBinary(data) {
+  var buff;
+  var transport = new thrift.TBufferedTransport(null, function(msg){
+    buff = msg;
+  });
+  var prot = new thrift.TBinaryProtocol(transport);
+  data.write(prot);
+  prot.flush();
+  return buff;
+
+}
+
+
+function deserializeBinary(serialized, type) {
+  var t = new thrift.TFramedTransport(serialized);
+  var p = new thrift.TBinaryProtocol(t);
+  var data = new type();
+  data.read(p);
+  return data;
+}
+
+
+function serializeJSON(data) {
+  var buff;
+  var transport = new thrift.TBufferedTransport(null, function(msg){
+    buff = msg;
+  });
+  var protocol  = new thrift.TJSONProtocol(transport);
+  protocol.writeMessageBegin("", 0, 0);
+  data.write(protocol);
+  protocol.writeMessageEnd();
+  protocol.flush();
+  return buff;
+}
+
+
+function deserializeJSON(serialized, type) {
+  var transport = new thrift.TFramedTransport(serialized);
+  var protocol  = new thrift.TJSONProtocol(transport);
+  protocol.readMessageBegin();
+  var data = new type();
+  data.read(protocol);
+  protocol.readMessageEnd();
+  return data;
+}
+
+
+function createThriftObj() {
+
+  return new ttypes.Complex({
+
+    struct_field: new ttypes.Simple({value: 'a'}),
+
+    struct_list_field: [
+      new ttypes.Simple({value: 'b'}),
+      new ttypes.Simple({value: 'c'}),
+    ],
+
+    struct_set_field: [
+      new ttypes.Simple({value: 'd'}),
+      new ttypes.Simple({value: 'e'}),
+    ],
+
+    struct_map_field: {
+      A: new ttypes.Simple({value: 'f'}),
+      B: new ttypes.Simple({value: 'g'})
+    },
+
+    struct_nested_containers_field: [
+      [
+        {
+          C: [
+            new ttypes.Simple({value: 'h'}),
+            new ttypes.Simple({value: 'i'})
+          ]
+        }
+      ]
+    ],
+
+    struct_nested_containers_field2: {
+      D: [
+        {
+          DA: new ttypes.Simple({value: 'j'})
+        },
+        {
+          DB: new ttypes.Simple({value: 'k'})
+        }
+      ]
+    }
+  }
+  );
+}
+
+
+function createJsObj() {
+
+  return {
+
+    struct_field: {value: 'a'},
+
+    struct_list_field: [
+      {value: 'b'},
+      {value: 'c'},
+    ],
+
+    struct_set_field: [
+      {value: 'd'},
+      {value: 'e'},
+    ],
+
+    struct_map_field: {
+      A: {value: 'f'},
+      B: {value: 'g'}
+    },
+
+    struct_nested_containers_field: [
+      [
+        {
+          C: [
+            {value: 'h'},
+            {value: 'i'}
+          ]
+        }
+      ]
+    ],
+
+    struct_nested_containers_field2: {
+      D: [
+        {
+          DA: {value: 'j'}
+        },
+        {
+          DB: {value: 'k'}
+        }
+      ]
+    }
+  };
+}
+
+
+function assertValues(obj, assert) {
+    assert.equals(obj.struct_field.value, 'a');
+    assert.equals(obj.struct_list_field[0].value, 'b');
+    assert.equals(obj.struct_list_field[1].value, 'c');
+    assert.equals(obj.struct_set_field[0].value, 'd');
+    assert.equals(obj.struct_set_field[1].value, 'e');
+    assert.equals(obj.struct_map_field.A.value, 'f');
+    assert.equals(obj.struct_map_field.B.value, 'g');
+    assert.equals(obj.struct_nested_containers_field[0][0].C[0].value, 'h');
+    assert.equals(obj.struct_nested_containers_field[0][0].C[1].value, 'i');
+    assert.equals(obj.struct_nested_containers_field2.D[0].DA.value, 'j');
+    assert.equals(obj.struct_nested_containers_field2.D[1].DB.value, 'k');
+}
+
+function createTestCases(serialize, deserialize) {
+
+  var cases = {
+
+    "Serialize/deserialize should return equal object": function(assert){
+      var tObj = createThriftObj();
+      var received = deserialize(serialize(tObj), ttypes.Complex);
+      assert.ok(tObj !== received, 'not the same object');
+      assert.deepEqual(tObj, received);
+      assert.end();
+    },
+
+    "Nested structs and containers initialized from plain js objects should serialize same as if initialized from thrift objects": function(assert) {
+      var tObj1 = createThriftObj();
+      var tObj2 = new ttypes.Complex(createJsObj());
+      assertValues(tObj2, assert);
+      var s1 = serialize(tObj1);
+      var s2 = serialize(tObj2);
+      assert.ok(bufferEquals(s1, s2));
+      assert.end();
+    },
+
+    "Modifications to args object should not affect constructed Thrift object": function (assert) {
+
+      var args = createJsObj();
+      assertValues(args, assert);
+
+      var tObj = new ttypes.Complex(args);
+      assertValues(tObj, assert);
+
+      args.struct_field.value = 'ZZZ';
+      args.struct_list_field[0].value = 'ZZZ';
+      args.struct_list_field[1].value = 'ZZZ';
+      args.struct_set_field[0].value = 'ZZZ';
+      args.struct_set_field[1].value = 'ZZZ';
+      args.struct_map_field.A.value = 'ZZZ';
+      args.struct_map_field.B.value = 'ZZZ';
+      args.struct_nested_containers_field[0][0].C[0] = 'ZZZ';
+      args.struct_nested_containers_field[0][0].C[1] = 'ZZZ';
+      args.struct_nested_containers_field2.D[0].DA = 'ZZZ';
+      args.struct_nested_containers_field2.D[0].DB = 'ZZZ';
+
+      assertValues(tObj, assert);
+      assert.end();
+    },
+
+    "nulls are ok": function(assert) {
+      var tObj = new ttypes.Complex({
+        struct_field: null,
+        struct_list_field: null,
+        struct_set_field: null,
+        struct_map_field: null,
+        struct_nested_containers_field: null,
+        struct_nested_containers_field2: null
+      });
+      var received = deserialize(serialize(tObj), ttypes.Complex);
+      assert.ok(tObj !== received);
+      assert.deepEqual(tObj, received);
+      assert.end();
+    }
+
+  };
+  return cases;
+}
+
+
+function run(name, cases){
+  Object.keys(cases).forEach(function(caseName) {
+    test(name + ': ' + caseName, cases[caseName]);
+  });
+}
+
+run('binary', createTestCases(serializeBinary, deserializeBinary));
+run('json', createTestCases(serializeJSON, deserializeJSON));

http://git-wip-us.apache.org/repos/asf/thrift/blob/15d90424/lib/nodejs/test/test-cases.js
----------------------------------------------------------------------
diff --git a/lib/nodejs/test/test-cases.js b/lib/nodejs/test/test-cases.js
index c396ca9..7872295 100644
--- a/lib/nodejs/test/test-cases.js
+++ b/lib/nodejs/test/test-cases.js
@@ -107,6 +107,22 @@ var crazy = new ttypes.Insanity({
     })]
 });
 
+var crazy2 = new ttypes.Insanity({
+  "userMap":{ "5":5, "8":8 },
+  "xtructs":[{
+      "string_thing":"Goodbye4",
+      "byte_thing":4,
+      "i32_thing":4,
+      "i64_thing":4
+    }, {
+      "string_thing":"Hello2",
+      "byte_thing":2,
+      "i32_thing":2,
+      "i64_thing":2
+    }]
+});
+
+
 var insanity = {
   "1":{ "2": crazy, "3": crazy },
   "2":{ "6":{ "userMap":{}, "xtructs":[] } }
@@ -119,4 +135,5 @@ module.exports.deep = deep;
 module.exports.out = out;
 module.exports.out2 = out2;
 module.exports.crazy = crazy;
+module.exports.crazy2 = crazy2;
 module.exports.insanity = insanity;

http://git-wip-us.apache.org/repos/asf/thrift/blob/15d90424/lib/nodejs/test/testAll.sh
----------------------------------------------------------------------
diff --git a/lib/nodejs/test/testAll.sh b/lib/nodejs/test/testAll.sh
index fd11425..38b284a 100755
--- a/lib/nodejs/test/testAll.sh
+++ b/lib/nodejs/test/testAll.sh
@@ -71,10 +71,12 @@ TESTOK=0
 #generating thrift code
 
 ${DIR}/../../../compiler/cpp/thrift -o ${DIR} --gen js:node ${DIR}/../../../test/ThriftTest.thrift
+${DIR}/../../../compiler/cpp/thrift -o ${DIR} --gen js:node ${DIR}/../../../test/JsDeepConstructorTest.thrift
 
 #unit tests
 
 node ${DIR}/binary.test.js || TESTOK=1
+node ${DIR}/deep-constructor.test.js || TESTOK=1
 
 #integration tests
 

http://git-wip-us.apache.org/repos/asf/thrift/blob/15d90424/lib/nodejs/test/test_driver.js
----------------------------------------------------------------------
diff --git a/lib/nodejs/test/test_driver.js b/lib/nodejs/test/test_driver.js
index 6e472ad..27ffd63 100644
--- a/lib/nodejs/test/test_driver.js
+++ b/lib/nodejs/test/test_driver.js
@@ -80,6 +80,11 @@ exports.ThriftTestDriver = function(client, callback) {
       checkRecursively(testCases.insanity, response, 'testInsanity');
     });
 
+    client.testInsanity(testCases.crazy2, function(err, response) {
+      assert.error(err, 'testInsanity2: no callback error');
+      checkRecursively(testCases.insanity, response, 'testInsanity2');
+    });
+
     client.testException('TException', function(err, response) {
       assert.ok(err instanceof TException, 'testException: correct error type');
       assert.ok(!response, 'testException: no response');
@@ -161,6 +166,12 @@ exports.ThriftTestDriverPromise = function(client, callback) {
       })
       .fail(fail('testInsanity'));
 
+    client.testInsanity(testCases.crazy2)
+      .then(function(response) {
+        checkRecursively(testCases.insanity, response, 'testInsanity2');
+      })
+      .fail(fail('testInsanity2'));
+
     client.testException('TException')
       .then(function(response) {
         fail('testException: TException');

http://git-wip-us.apache.org/repos/asf/thrift/blob/15d90424/package.json
----------------------------------------------------------------------
diff --git a/package.json b/package.json
index 0fbb80e..c4688b6 100644
--- a/package.json
+++ b/package.json
@@ -35,6 +35,7 @@
     "ws": "~0.4.32"
   },
   "devDependencies": {
+    "buffer-equals": "^1.0.3",
     "commander": "2.1.x",
     "connect": "2.7.x",
     "istanbul": "^0.3.5",

http://git-wip-us.apache.org/repos/asf/thrift/blob/15d90424/test/JsDeepConstructorTest.thrift
----------------------------------------------------------------------
diff --git a/test/JsDeepConstructorTest.thrift b/test/JsDeepConstructorTest.thrift
new file mode 100644
index 0000000..9150854
--- /dev/null
+++ b/test/JsDeepConstructorTest.thrift
@@ -0,0 +1,12 @@
+struct Simple {
+  1: string value
+}
+
+struct Complex {
+  1: Simple struct_field
+  2: list<Simple> struct_list_field
+  3: set<Simple> struct_set_field
+  4: map<string,Simple> struct_map_field
+  5: list<set<map<string,list<Simple>>>> struct_nested_containers_field
+  6: map<string, list<map<string,Simple>> > struct_nested_containers_field2
+}