From c76dae5b06724d972df9192e200ed6ff39ce747a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=BCrg=20Lehni?= <juerg@scratchdisk.com>
Date: Fri, 1 Nov 2013 11:26:11 +0100
Subject: [PATCH] Implement a new strategy for importing JSON, where it is
 imported into the item itself, not its children list, if the class match.

---
 src/core/Base.js | 45 +++++++++++++++++++++++++--------------------
 src/item/Item.js | 23 +++++++++++++++--------
 2 files changed, 40 insertions(+), 28 deletions(-)

diff --git a/src/core/Base.js b/src/core/Base.js
index 16b533d3..7680b61d 100644
--- a/src/core/Base.js
+++ b/src/core/Base.js
@@ -390,46 +390,51 @@ Base.inject(/** @lends Base# */{
 		 * deserializable types through Base.exports, and the values following
 		 * in the array are the arguments to their initialize function.
 		 * Any other value is passed on unmodified.
-		 * The passed data is recoursively traversed and converted, leaves first
+		 * The passed json data is recoursively traversed and converted, leaves
+		 * first
 		 */
-		deserialize: function(obj, data) {
-			var res = obj;
-			// A data side-car to deserialize that can hold any kind of 'global'
-			// data across a deserialization. It's currently just used to hold
-			// dictionary definitions.
-			data = data || {};
-			if (Array.isArray(obj)) {
+		deserialize: function(json, target, _data) {
+			var res = json;
+			// A _data side-car to deserialize that can hold any kind of
+			// 'global' data across a deserialization. It's currently only used
+			// to hold dictionary definitions.
+			_data = _data || {};
+			if (Array.isArray(json)) {
 				// See if it's a serialized type. If so, the rest of the array
 				// are the arguments to #initialize(). Either way, we simply
 				// deserialize all elements of the array.
-				var type = obj[0],
+				var type = json[0],
 					// Handle stored dictionary specially, since we need to
 					// keep is a lookup table to retrieve referenced items from.
 					isDictionary = type === 'dictionary';
 				if (!isDictionary) {
 					// First see if this is perhaps a dictionary reference, and
 					// if so return its definition instead.
-					if (data.dictionary && obj.length == 1 && /^#/.test(type))
-						return data.dictionary[type];
+					if (_data.dictionary && json.length == 1 && /^#/.test(type))
+						return _data.dictionary[type];
 					type = Base.exports[type];
 				}
 				res = [];
 				// Skip first type entry for arguments
-				for (var i = type ? 1 : 0, l = obj.length; i < l; i++)
-					res.push(Base.deserialize(obj[i], data));
+				for (var i = type ? 1 : 0, l = json.length; i < l; i++)
+					res.push(Base.deserialize(json[i], null, _data));
 				if (isDictionary) {
-					data.dictionary = res[0];
+					_data.dictionary = res[0];
 				} else if (type) {
 					// Create serialized type and pass collected arguments to
 					// constructor().
 					var args = res;
-					res = Base.create(type.prototype);
+					// If a target is provided and its of the right type,
+					// import right into it.
+					res = target instanceof type
+							? target
+							: Base.create(type.prototype);
 					type.apply(res, args);
 				}
-			} else if (Base.isPlainObject(obj)) {
+			} else if (Base.isPlainObject(json)) {
 				res = {};
-				for (var key in obj)
-					res[key] = Base.deserialize(obj[key], data);
+				for (var key in json)
+					res[key] = Base.deserialize(json[key], null, _data);
 			}
 			return res;
 		},
@@ -438,9 +443,9 @@ Base.inject(/** @lends Base# */{
 			return JSON.stringify(Base.serialize(obj, options));
 		},
 
-		importJSON: function(json) {
+		importJSON: function(json, target) {
 			return Base.deserialize(
-					typeof json === 'string' ? JSON.parse(json) : json);
+					typeof json === 'string' ? JSON.parse(json) : json, target);
 		},
 
 		/**
diff --git a/src/item/Item.js b/src/item/Item.js
index 3858f3e8..6b971617 100644
--- a/src/item/Item.js
+++ b/src/item/Item.js
@@ -1306,9 +1306,9 @@ var Item = Base.extend(Callback, /** @lends Item# */{
 		// Insert is true by default.
 		if (insert || insert === undefined)
 			copy.insertAbove(this);
-		// Only copy over these fields if they are actually defined in 'this'
-		// TODO: Consider moving this to Base once it's useful in more than one
-		// place
+		// Only copy over these fields if they are actually defined in 'this',
+		// meaning the default value has been overwritten (default is on
+		// prototype).
 		var keys = ['_locked', '_visible', '_blendMode', '_opacity',
 				'_clipMask', '_guide'];
 		for (var i = 0, l = keys.length; i < l; i++) {
@@ -1649,15 +1649,22 @@ var Item = Base.extend(Callback, /** @lends Item# */{
 	 */
 
 	/**
-	 * Imports (deserializes) the stored JSON data into this item's
-	 * {@link Item#children} list.
-	 * Note that the item is not cleared first. You can call
-	 * {@link Item#removeChildren()} to do so.
+	 * Imports (deserializes) the stored JSON data into this item. If the data
+	 * describes an item of the same class or a parent class of the item, the
+	 * data is imported into the item itself. If not, the imported item is added
+	 * to this item's {@link Item#children} list. Note that not all type of
+	 * items can have children.
 	 *
 	 * @param {String} json the JSON data to import from.
 	 */
 	importJSON: function(json) {
-		return this.addChild(Base.importJSON(json));
+		// Try importing into `this`. If another item is returned, try adding
+		// it as a child (this won't be successful on some classes, returning
+		// null).
+		var res = Base.importJSON(json, this);
+		return res !== this
+				? this.addChild(res)
+				: res;
 	},
 
 	/**