mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-01 02:38:43 -05:00
JSON: Improve serialization and deserialization on objects other than Item.
Closes #392
This commit is contained in:
parent
75c40babc9
commit
56dd636f22
4 changed files with 78 additions and 30 deletions
|
@ -46,6 +46,17 @@ Base.inject(/** @lends Base# */{
|
||||||
return this._class || '';
|
return this._class || '';
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imports (deserializes) the stored JSON data into the object, if the
|
||||||
|
* classes match. If they do not match, a newly created object is returned
|
||||||
|
* instead.
|
||||||
|
*
|
||||||
|
* @param {String} json the JSON data to import from
|
||||||
|
*/
|
||||||
|
importJSON: function(json) {
|
||||||
|
return Base.importJSON(json, this);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exports (serializes) this object to a JSON data object or string.
|
* Exports (serializes) this object to a JSON data object or string.
|
||||||
*
|
*
|
||||||
|
@ -332,9 +343,9 @@ Base.inject(/** @lends Base# */{
|
||||||
serialize: function(obj, options, compact, dictionary) {
|
serialize: function(obj, options, compact, dictionary) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
|
|
||||||
var root = !dictionary,
|
var isRoot = !dictionary,
|
||||||
res;
|
res;
|
||||||
if (root) {
|
if (isRoot) {
|
||||||
options.formatter = new Formatter(options.precision);
|
options.formatter = new Formatter(options.precision);
|
||||||
// Create a simple dictionary object that handles all the
|
// Create a simple dictionary object that handles all the
|
||||||
// storing and retrieving of dictionary definitions and
|
// storing and retrieving of dictionary definitions and
|
||||||
|
@ -376,17 +387,17 @@ Base.inject(/** @lends Base# */{
|
||||||
// identifier), see if _serialize didn't already add the class,
|
// identifier), see if _serialize didn't already add the class,
|
||||||
// e.g. for classes that do not support compact form.
|
// e.g. for classes that do not support compact form.
|
||||||
var name = obj._class;
|
var name = obj._class;
|
||||||
if (name && !compact && !res._compact && res[0] !== name)
|
// Enforce class names on root level, except if the class
|
||||||
|
// explicitly asks to be serialized in compact form (Project).
|
||||||
|
if (name && !obj._compactSerialize && (isRoot || !compact)
|
||||||
|
&& res[0] !== name) {
|
||||||
res.unshift(name);
|
res.unshift(name);
|
||||||
|
}
|
||||||
} else if (Array.isArray(obj)) {
|
} else if (Array.isArray(obj)) {
|
||||||
res = [];
|
res = [];
|
||||||
for (var i = 0, l = obj.length; i < l; i++)
|
for (var i = 0, l = obj.length; i < l; i++)
|
||||||
res[i] = Base.serialize(obj[i], options, compact,
|
res[i] = Base.serialize(obj[i], options, compact,
|
||||||
dictionary);
|
dictionary);
|
||||||
// Mark array as compact, so obj._serialize handling above
|
|
||||||
// doesn't add the class name again.
|
|
||||||
if (compact)
|
|
||||||
res._compact = true;
|
|
||||||
} else if (Base.isPlainObject(obj)) {
|
} else if (Base.isPlainObject(obj)) {
|
||||||
res = {};
|
res = {};
|
||||||
var keys = Object.keys(obj);
|
var keys = Object.keys(obj);
|
||||||
|
@ -400,7 +411,7 @@ Base.inject(/** @lends Base# */{
|
||||||
} else {
|
} else {
|
||||||
res = obj;
|
res = obj;
|
||||||
}
|
}
|
||||||
return root && dictionary.length > 0
|
return isRoot && dictionary.length > 0
|
||||||
? [['dictionary', dictionary.definitions], res]
|
? [['dictionary', dictionary.definitions], res]
|
||||||
: res;
|
: res;
|
||||||
},
|
},
|
||||||
|
@ -414,9 +425,11 @@ Base.inject(/** @lends Base# */{
|
||||||
* The passed json data is recoursively traversed and converted, leaves
|
* The passed json data is recoursively traversed and converted, leaves
|
||||||
* first
|
* first
|
||||||
*/
|
*/
|
||||||
deserialize: function(json, create, _data, _isDictionary) {
|
deserialize: function(json, create, _data, _setDictionary, _isRoot) {
|
||||||
var res = json,
|
var res = json,
|
||||||
isRoot = !_data;
|
isFirst = !_data,
|
||||||
|
hasDictionary = isFirst && json && json.length
|
||||||
|
&& json[0][0] === 'dictionary';
|
||||||
// A _data side-car to deserialize that can hold any kind of
|
// A _data side-car to deserialize that can hold any kind of
|
||||||
// 'global' data across a deserialization. It's currently only used
|
// 'global' data across a deserialization. It's currently only used
|
||||||
// to hold dictionary definitions.
|
// to hold dictionary definitions.
|
||||||
|
@ -431,19 +444,18 @@ Base.inject(/** @lends Base# */{
|
||||||
isDictionary = type === 'dictionary';
|
isDictionary = type === 'dictionary';
|
||||||
// First see if this is perhaps a dictionary reference, and
|
// First see if this is perhaps a dictionary reference, and
|
||||||
// if so return its definition instead.
|
// if so return its definition instead.
|
||||||
if (json.length == 1 && /^#/.test(type))
|
if (json.length == 1 && /^#/.test(type)) {
|
||||||
return _data.dictionary[type];
|
return _data.dictionary[type];
|
||||||
|
}
|
||||||
type = Base.exports[type];
|
type = Base.exports[type];
|
||||||
res = [];
|
res = [];
|
||||||
// We need to set the dictionary object before further
|
|
||||||
// deserialization, because serialized symbols may contain
|
|
||||||
// references to serialized gradients
|
|
||||||
if (_isDictionary)
|
|
||||||
_data.dictionary = res;
|
|
||||||
// Skip first type entry for arguments
|
// Skip first type entry for arguments
|
||||||
for (var i = type ? 1 : 0, l = json.length; i < l; i++)
|
// Pass true for _isRoot in children if we have a dictionary,
|
||||||
|
// in which case we need to shift the root level one down.
|
||||||
|
for (var i = type ? 1 : 0, l = json.length; i < l; i++) {
|
||||||
res.push(Base.deserialize(json[i], create, _data,
|
res.push(Base.deserialize(json[i], create, _data,
|
||||||
isDictionary));
|
isDictionary, hasDictionary));
|
||||||
|
}
|
||||||
if (type) {
|
if (type) {
|
||||||
// Create serialized type and pass collected arguments to
|
// Create serialized type and pass collected arguments to
|
||||||
// constructor().
|
// constructor().
|
||||||
|
@ -452,7 +464,7 @@ Base.inject(/** @lends Base# */{
|
||||||
// creation. This is used in #importJSON() to pass
|
// creation. This is used in #importJSON() to pass
|
||||||
// on insert = false to all items except layers.
|
// on insert = false to all items except layers.
|
||||||
if (create) {
|
if (create) {
|
||||||
res = create(type, args, isRoot);
|
res = create(type, args, isFirst || _isRoot);
|
||||||
} else {
|
} else {
|
||||||
res = Base.create(type.prototype);
|
res = Base.create(type.prototype);
|
||||||
type.apply(res, args);
|
type.apply(res, args);
|
||||||
|
@ -460,16 +472,16 @@ Base.inject(/** @lends Base# */{
|
||||||
}
|
}
|
||||||
} else if (Base.isPlainObject(json)) {
|
} else if (Base.isPlainObject(json)) {
|
||||||
res = {};
|
res = {};
|
||||||
// See above why we have to set this before Base.deserialize()
|
// We need to set the dictionary object before further
|
||||||
if (_isDictionary)
|
// deserialization, because serialized symbols may contain
|
||||||
|
// references to serialized gradients
|
||||||
|
if (_setDictionary)
|
||||||
_data.dictionary = res;
|
_data.dictionary = res;
|
||||||
for (var key in json)
|
for (var key in json)
|
||||||
res[key] = Base.deserialize(json[key], create, _data);
|
res[key] = Base.deserialize(json[key], create, _data);
|
||||||
}
|
}
|
||||||
// Filter out deserialized dictionary.
|
// Filter out deserialized dictionary:
|
||||||
return isRoot && json && json.length && json[0][0] === 'dictionary'
|
return hasDictionary ? res[1] : res;
|
||||||
? res[1]
|
|
||||||
: res;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
exportJSON: function(obj, options) {
|
exportJSON: function(obj, options) {
|
||||||
|
@ -491,9 +503,11 @@ Base.inject(/** @lends Base# */{
|
||||||
&& target.constructor === ctor,
|
&& target.constructor === ctor,
|
||||||
obj = useTarget ? target
|
obj = useTarget ? target
|
||||||
: Base.create(ctor.prototype),
|
: Base.create(ctor.prototype),
|
||||||
// When reusing an object, try to initialize it
|
// When reusing an object, try to (re)initialize it
|
||||||
// through _initialize (Item), fall-back to _set.
|
// through _initialize (Item), fall-back to
|
||||||
init = useTarget ? obj._initialize || obj._set
|
// initialize (Color & co), then _set.
|
||||||
|
init = useTarget
|
||||||
|
? obj._initialize || obj.initialize || obj._set
|
||||||
: ctor;
|
: ctor;
|
||||||
// NOTE: We don't set insert false for layers since we
|
// NOTE: We don't set insert false for layers since we
|
||||||
// want these to be created on the fly in the active
|
// want these to be created on the fly in the active
|
||||||
|
|
|
@ -34,6 +34,7 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
|
||||||
_class: 'Project',
|
_class: 'Project',
|
||||||
_list: 'projects',
|
_list: 'projects',
|
||||||
_reference: 'project',
|
_reference: 'project',
|
||||||
|
_compactSerialize: true, // Never include the class name for Project
|
||||||
|
|
||||||
// TODO: Add arguments to define pages
|
// TODO: Add arguments to define pages
|
||||||
/**
|
/**
|
||||||
|
@ -75,7 +76,6 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
|
||||||
// into the active project automatically. We might want to add proper
|
// into the active project automatically. We might want to add proper
|
||||||
// project serialization later, but deserialization of a layers array
|
// project serialization later, but deserialization of a layers array
|
||||||
// will always work.
|
// will always work.
|
||||||
// Pass true for compact, so 'Project' does not get added as the class
|
|
||||||
return Base.serialize(this._children, options, true, dictionary);
|
return Base.serialize(this._children, options, true, dictionary);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -631,7 +631,7 @@ var Color = Base.extend(new function() {
|
||||||
_serialize: function(options, dictionary) {
|
_serialize: function(options, dictionary) {
|
||||||
var components = this.getComponents();
|
var components = this.getComponents();
|
||||||
return Base.serialize(
|
return Base.serialize(
|
||||||
// We can ommit the type for gray and rgb:
|
// We can omit the type for gray and rgb:
|
||||||
/^(gray|rgb)$/.test(this._type)
|
/^(gray|rgb)$/.test(this._type)
|
||||||
? components
|
? components
|
||||||
: [this._type].concat(components),
|
: [this._type].concat(components),
|
||||||
|
|
|
@ -207,3 +207,37 @@ test('Color', function() {
|
||||||
});
|
});
|
||||||
testExportImportJSON(paper.project);
|
testExportImportJSON(paper.project);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Color#importJSON()', function() {
|
||||||
|
var topLeft = [100, 100];
|
||||||
|
var bottomRight = [200, 200];
|
||||||
|
|
||||||
|
var path = new Path.Rectangle({
|
||||||
|
topLeft: topLeft,
|
||||||
|
bottomRight: bottomRight,
|
||||||
|
// Fill the path with a gradient of three color stops
|
||||||
|
// that runs between the two points we defined earlier:
|
||||||
|
fillColor: {
|
||||||
|
gradient: {
|
||||||
|
stops: ['yellow', 'red', 'blue']
|
||||||
|
},
|
||||||
|
origin: topLeft,
|
||||||
|
destination: bottomRight
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var json = path.fillColor.exportJSON(),
|
||||||
|
id = path.fillColor.gradient._id,
|
||||||
|
color = new Color(),
|
||||||
|
str = '[["dictionary",{"#' + id + '":["Gradient",[[[1,1,0],0],[[1,0,0],0.5],[[0,0,1],1]],false]}],["Color","gradient",["#' + id + '"],[100,100],[200,200]]]';
|
||||||
|
|
||||||
|
equals(json, str);
|
||||||
|
|
||||||
|
equals(function() {
|
||||||
|
return color.importJSON(json) === color;
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
equals(function() {
|
||||||
|
return color.equals(path.fillColor);
|
||||||
|
}, true);
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in a new issue