Implemented toJSON and fromJSON and a reviver for most data types

This commit is contained in:
jos 2015-02-18 20:34:43 +01:00
parent 8892a8c026
commit 4bd7dc5633
20 changed files with 495 additions and 67 deletions

View File

@ -13,11 +13,11 @@ module.exports = function (math) {
*
* Examples:
*
* math.clone(3.5); // returns number 3.5
* math.clone(2 - 4i); // returns Complex 2 - 4i
* math.clone(45 deg); // returns Unit 45 deg
* math.clone([[1, 2], [3, 4]]); // returns Array [[1, 2], [3, 4]]
* math.clone("hello world"); // returns string "hello world"
* math.clone(3.5); // returns number 3.5
* math.clone(math.complex('2 - 4i'); // returns Complex 2 - 4i
* math.clone(math.unit(45, 'deg')); // returns Unit 45 deg
* math.clone([[1, 2], [3, 4]]); // returns Array [[1, 2], [3, 4]]
* math.clone("hello world"); // returns string "hello world"
*
* @param {*} x Object to be cloned
* @return {*} A clone of object x

34
lib/json/reviver.js Normal file
View File

@ -0,0 +1,34 @@
'use strict';
var BigNumber = require('../type/BigNumber');
var Complex = require('../type/Complex');
var Index = require('../type/Index');
var Matrix = require('../type/Matrix');
var Range = require('../type/Range');
var ResultSet = require('../type/ResultSet');
var Unit = require('../type/Unit');
/**
* Instantiate mathjs data types from their JSON representation
* @param {string} key
* @param {*} value
* @returns {*} Returns the revived object
*/
function reviver(key, value) {
var type = value && value['@type'];
switch (type) {
case 'BigNumber': return BigNumber.fromJSON(value);
case 'Complex': return Complex.fromJSON(value);
case 'Index': return Index.fromJSON(value);
case 'Matrix': return Matrix.fromJSON(value);
case 'Range': return Range.fromJSON(value);
case 'ResultSet': return ResultSet.fromJSON(value);
case 'Unit': return Unit.fromJSON(value);
// TODO: add Help
}
return value;
}
module.exports = reviver;

View File

@ -171,6 +171,11 @@ function create (config) {
math.expression.Parser = require('./expression/Parser');
math.expression.docs = require('./expression/docs/index');
// serialization utilities
math.json = {
reviver: require('./json/reviver')
};
// expression parser
require('./function/expression/compile')(math, _config);
require('./function/expression/eval')(math, _config);

28
lib/type/BigNumber.js Normal file
View File

@ -0,0 +1,28 @@
var BigNumber = require('decimal.js');
// FIXME: replace all require('decimal.js') with require('./BigNumber').
/**
* Get a JSON representation of a BigNumber containing
* type information
* @returns {Object} Returns a JSON object structured as:
* `{"@type": "BigNumber", "value": "0.2"}`
*/
BigNumber.prototype.toJSON = function () {
return {
'@type': 'BigNumber',
value: this.toString()
};
};
/**
* Instantiate a BigNumber from a JSON object
* @param {Object} json a JSON object structured as:
* `{"@type": "BigNumber", "value": "0.2"}`
* @return {BigNumber}
*/
BigNumber.fromJSON = function (json) {
return new BigNumber(json.value);
};
module.exports = BigNumber;

View File

@ -453,14 +453,14 @@ Complex.prototype.toJSON = function () {
/**
* Create a Complex number from a JSON object
* @param {Object} obj A JSON Object structured as
* @param {Object} json A JSON Object structured as
* {"@type": "Complex", "re": 2, "im": 3}
* All properties are optional, default values
* for `re` and `im` are 0.
* @return {Complex} Returns a new Complex number
*/
Complex.fromJSON = function (obj) {
return new Complex(obj);
Complex.fromJSON = function (json) {
return new Complex(json);
};
/**

View File

@ -94,12 +94,12 @@ Help.prototype.toJSON = function () {
/**
* Instantiate a Help object from a JSON object
* @param {Object} obj
* @param {Object} json
* @param {Object} math An instance of mathjs
* @returns {Help} Returns a new Help object
*/
Help.prototype.fromJSON = function (obj, math) {
return new Help(obj, math);
Help.prototype.fromJSON = function (json, math) {
return new Help(json, math);
};
/**

View File

@ -265,5 +265,27 @@ Index.prototype.toString = function () {
return '[' + strings.join(', ') + ']';
};
/**
* Get a JSON representation of the Index
* @returns {Object} Returns a JSON object structured as:
* `{"@type": "Index", "ranges": [{"@type": "Range", start: 0, end: 10, step:1}, ...]}`
*/
Index.prototype.toJSON = function () {
return {
'@type': 'Index',
ranges: this._ranges
};
};
/**
* Instantiate an Index from a JSON object
* @param {Object} json A JSON object structured as:
* `{"@type": "Index", "ranges": [{"@type": "Range", start: 0, end: 10, step:1}, ...]}`
* @return {Index}
*/
Index.fromJSON = function (json) {
return Index.create(json.ranges);
};
// exports
module.exports = Index;

View File

@ -492,40 +492,25 @@ Matrix.prototype.toString = function () {
};
/**
* Get a JSON representation of the matrix data.
* @returns {Object} Returns an object like
* {"@type": "Matrix", "data": [...]}
* Get a JSON representation of the matrix
* @returns {Object}
*/
Matrix.prototype.toJSON = function () {
return {
"@type": "Matrix",
"data": this.map(function (entry) {
return entry && typeof entry.toJSON === 'function' ? entry.toJSON() : entry;
}).toArray()
"data": this._data
}
};
/**
* Generate a matrix from a JSON object
* @param {Object || Array} obj An object structured like
* `{"@type": "Matrix", data: []}`,
* or is an array `[...]`
* where all entries are supposed to be
* JSON objects of data types.
* @param {Object} math An instance of mathjs, used to find
* available types under `math.type`
* @returns {Object} Returns an object like
* {"@type": "Matrix", "data": [...]}
* @param {Object} json An object structured like
* `{"@type": "Matrix", data: []}`,
* where @type is optional
* @returns {Matrix}
*/
Matrix.fromJSON = function (obj, math) {
var matrix = new Matrix(isArray(obj) ? obj : obj.data);
return matrix.map(function (entry) {
var type = entry && entry['@type'];
var constructor = type && math.type[type];
var fromJSON = constructor && constructor.fromJSON;
return fromJSON ? fromJSON(entry, math) : entry;
});
Matrix.fromJSON = function (json) {
return new Matrix(json.data);
};
/**

View File

@ -265,5 +265,29 @@ Range.prototype.toString = function () {
return this.format();
};
/**
* Get a JSON representation of the range
* @returns {Object} Returns a JSON object structured as:
* `{"@type": "Range", "start": 2, "end": 4, "step": 1}`
*/
Range.prototype.toJSON = function () {
return {
'@type': 'Range',
start: this.start,
end: this.end,
step: this.step
};
};
/**
* Instantiate a Range from a JSON object
* @param {Object} json A JSON object structured as:
* `{"@type": "Range", "start": 2, "end": 4, "step": 1}`
* @return {Range}
*/
Range.fromJSON = function (json) {
return new Range(json.start, json.end, json.step);
};
// exports
module.exports = Range;

View File

@ -29,4 +29,26 @@ ResultSet.prototype.toString = function () {
return '[' + this.entries.join(', ') + ']';
};
/**
* Get a JSON representation of the ResultSet
* @returns {Object} Returns a JSON object structured as:
* `{"@type": "ResultSet", "entries": [...]}`
*/
ResultSet.prototype.toJSON = function () {
return {
'@type': 'ResultSet',
entries: this.entries
};
};
/**
* Instantiate a ResultSet from a JSON object
* @param {Object} json A JSON object structured as:
* `{"@type": "ResultSet", "entries": [...]}`
* @return {ResultSet}
*/
ResultSet.fromJSON = function (json) {
return new ResultSet(json.entries);
};
module.exports = ResultSet;

View File

@ -259,13 +259,13 @@ Unit.prototype._normalize = function(value) {
};
/**
* Unnormalize a value, based on its currently set unit
* Denormalize a value, based on its currently set unit
* @param {Number} value
* @param {Number} [prefixValue] Optional prefix value to be used
* @return {Number} unnormalized value
* @return {Number} denormalized value
* @private
*/
Unit.prototype._unnormalize = function (value, prefixValue) {
Unit.prototype._denormalize = function (value, prefixValue) {
if (prefixValue == undefined) {
return value / this.unit.value / this.prefix.value - this.unit.offset;
}
@ -384,7 +384,7 @@ Unit.prototype.to = function (valuelessUnit) {
*/
Unit.prototype.toNumber = function (valuelessUnit) {
var other = this.to(valuelessUnit);
return other._unnormalize(other.value, other.prefix.value);
return other._denormalize(other.value, other.prefix.value);
};
@ -396,6 +396,32 @@ Unit.prototype.toString = function() {
return this.format();
};
/**
* Get a JSON representation of the unit
* @returns {Object} Returns a JSON object structured as:
* `{"@type": "Unit", "value": 2, "unit": "cm", "fixPrefix": false}`
*/
Unit.prototype.toJSON = function () {
return {
'@type': 'Unit',
value: this._denormalize(this.value),
unit: this.prefix.name + this.unit.name,
fixPrefix: this.fixPrefix
};
};
/**
* Instantiate a Unit from a JSON object
* @param {Object} json A JSON object structured as:
* `{"@type": "Unit", "value": 2, "unit": "cm", "fixPrefix": false}`
* @return {Unit}
*/
Unit.fromJSON = function (json) {
var unit = new Unit(json.value, json.unit);
unit.fixPrefix = json.fixPrefix || false;
return unit;
};
/**
* Returns the string representation of the unit.
* @return {String}
@ -416,12 +442,12 @@ Unit.prototype.format = function(options) {
if (this.value !== null && !this.fixPrefix) {
var bestPrefix = this._bestPrefix();
value = this._unnormalize(this.value, bestPrefix.value);
value = this._denormalize(this.value, bestPrefix.value);
str = number.format(value, options) + ' ';
str += bestPrefix.name + this.unit.name;
}
else {
value = this._unnormalize(this.value);
value = this._denormalize(this.value);
str = (this.value !== null) ? (number.format(value, options) + ' ') : '';
str += this.prefix.name + this.unit.name;
}

View File

@ -0,0 +1,80 @@
var assert= require('assert');
var Complex = require('../../lib/type/Complex');
var Range = require('../../lib/type/Range');
var Index = require('../../lib/type/Index');
var Matrix = require('../../lib/type/Matrix');
var Unit = require('../../lib/type/Unit');
var BigNumber = require('../../lib/type/BigNumber');
var ResultSet = require('../../lib/type/ResultSet');
describe('replacer', function () {
it('should stringify generic JSON', function () {
var data = {foo: [1,2,3], bar: null, baz: 'str'};
var json = '{"foo":[1,2,3],"bar":null,"baz":"str"}';
assert.deepEqual(JSON.stringify(data), json);
});
it('should stringify a Complex number', function () {
var c = new Complex(2, 4);
var json = '{"@type":"Complex","re":2,"im":4}';
assert.deepEqual(JSON.stringify(c), json);
});
it('should stringify a BigNumber', function () {
var b = new BigNumber(5);
var json = '{"@type":"BigNumber","value":"5"}';
assert.deepEqual(JSON.stringify(b), json);
});
it('should stringify a Range', function () {
var r = new Range(2, 10);
var json = '{"@type":"Range","start":2,"end":10,"step":1}';
assert.deepEqual(JSON.stringify(r), json);
});
it('should stringify an Index', function () {
var i = new Index([0, 10], 2);
var json = '{"@type":"Index","ranges":[' +
'{"@type":"Range","start":0,"end":10,"step":1},' +
'{"@type":"Range","start":2,"end":3,"step":1}' +
']}';
assert.deepEqual(JSON.stringify(i), json);
});
it('should stringify a Range (2)', function () {
var r = new Range(2, 10, 2);
var json = '{"@type":"Range","start":2,"end":10,"step":2}';
assert.deepEqual(JSON.stringify(r), json);
});
it('should stringify a Unit', function () {
var u = new Unit(5, 'cm');
var json = '{"@type":"Unit","value":5,"unit":"cm","fixPrefix":false}';
assert.deepEqual(JSON.stringify(u), json);
});
it('should stringify a Matrix', function () {
var m = new Matrix([[1,2],[3,4]]);
var json = '{"@type":"Matrix","data":[[1,2],[3,4]]}';
assert.deepEqual(JSON.stringify(m), json);
});
it('should stringify a ResultSet', function () {
var r = new ResultSet([1,2,new Complex(3,4)]);
var json = '{"@type":"ResultSet","entries":[1,2,{"@type":"Complex","re":3,"im":4}]}';
assert.deepEqual(JSON.stringify(r), json);
});
it('should stringify a Matrix containing a complex number', function () {
var c = new Complex(4, 5);
var m = new Matrix([[1,2],[3,c]]);
var json = '{"@type":"Matrix","data":[[1,2],[3,{"@type":"Complex","re":4,"im":5}]]}';
assert.deepEqual(JSON.stringify(m), json);
});
});

124
test/json/reviver.test.js Normal file
View File

@ -0,0 +1,124 @@
var assert= require('assert');
var reviver = require('../../lib/json/reviver');
var Complex = require('../../lib/type/Complex');
var Range = require('../../lib/type/Range');
var Index = require('../../lib/type/Index');
var Unit = require('../../lib/type/Unit');
var Matrix = require('../../lib/type/Matrix');
var BigNumber = require('../../lib/type/BigNumber');
var ResultSet = require('../../lib/type/ResultSet');
describe('reviver', function () {
it('should parse generic JSON', function () {
var json = '{"foo":[1,2,3],"bar":null,"baz":"str"}';
var data = {foo: [1,2,3], bar: null, baz: 'str'};
assert.deepEqual(JSON.parse(json, reviver), data);
});
it('should parse a stringified complex number', function () {
var json = '{"@type":"Complex","re":2,"im":4}';
var c = new Complex(2, 4);
var obj = JSON.parse(json, reviver);
assert(obj instanceof Complex);
assert.deepEqual(obj, c);
});
it('should parse a stringified BigNumber', function () {
var json = '{"@type":"BigNumber","value":"0.2"}';
var b = new BigNumber(0.2);
var obj = JSON.parse(json, reviver);
assert(obj instanceof BigNumber);
assert.deepEqual(obj, b);
});
it('should parse a stringified Range', function () {
var json = '{"@type":"Range","start":2,"end":10}';
var r = new Range(2, 10);
var obj = JSON.parse(json, reviver);
assert(obj instanceof Range);
assert.deepEqual(obj, r);
});
it('should parse a stringified Unit', function () {
var json = '{"@type":"Unit","value":5,"unit":"cm","fixPrefix":false}';
var u = new Unit(5, 'cm');
var obj = JSON.parse(json, reviver);
assert(obj instanceof Unit);
assert.deepEqual(obj, u);
});
it('should parse a stringified Range (2)', function () {
var json = '{"@type":"Range","start":2,"end":10,"step":2}';
var r = new Range(2, 10, 2);
var obj = JSON.parse(json, reviver);
assert(obj instanceof Range);
assert.deepEqual(obj, r);
});
it('should parse a stringified ResultSet', function () {
var json = '{"@type":"ResultSet","entries":[1,2,{"@type":"Complex","re":3,"im":4}]}';
var r = new ResultSet([1,2,new Complex(3,4)]);
var obj = JSON.parse(json, reviver);
assert(obj instanceof ResultSet);
assert.deepEqual(obj, r);
});
it('should parse a stringified Index', function () {
var json = '{"@type":"Index","ranges":[' +
'{"@type":"Range","start":0,"end":10,"step":1},' +
'{"@type":"Range","start":2,"end":3,"step":1}' +
']}';
var i = new Index([0, 10], 2);
var obj = JSON.parse(json, reviver);
assert(obj instanceof Index);
assert.deepEqual(obj, i);
});
it('should parse a stringified Index (2)', function () {
var json = '{"@type":"Index","ranges":[[0, 10],2]}';
var i = new Index([0, 10], 2);
var obj = JSON.parse(json, reviver);
assert(obj instanceof Index);
assert.deepEqual(obj, i);
});
it('should parse a stringified Matrix', function () {
var json = '{"@type":"Matrix","data":[[1,2],[3,4]]}';
var m = new Matrix([[1,2],[3,4]]);
var obj = JSON.parse(json, reviver);
assert(obj instanceof Matrix);
assert.deepEqual(obj, m);
});
it('should parse a stringified Matrix containing a complex number', function () {
var json = '{"@type":"Matrix","data":[[1,2],[3,{"@type":"Complex","re":4,"im":5}]]}';
var c = new Complex(4, 5);
var m = new Matrix([[1,2],[3,c]]);
var obj = JSON.parse(json, reviver);
assert(obj instanceof Matrix);
assert(obj._data[1][1] instanceof Complex);
assert.deepEqual(obj, m);
});
});

View File

@ -0,0 +1,18 @@
var assert = require('assert');
var BigNumber = require('../../lib/type/BigNumber');
describe('BigNumber', function () {
it('toJSON', function () {
assert.deepEqual(new BigNumber(5).toJSON(), {'@type': 'BigNumber', value: '5'});
});
it('fromJSON', function () {
var b = BigNumber.fromJSON({value: '5'});
assert.ok(b instanceof BigNumber);
assert.strictEqual(b.toString(), '5');
assert.deepEqual(b, new BigNumber(5));
});
});

View File

@ -1,8 +1,8 @@
// test data type Complex
var assert = require('assert'),
Unit = require('../../lib/type/Unit'),
Complex = require('../../lib/type/Complex');
var assert = require('assert');
var Unit = require('../../lib/type/Unit');
var Complex = require('../../lib/type/Complex');
describe('Complex', function () {
@ -304,12 +304,12 @@ describe('Complex', function () {
});
});
it('should return a JSON representation using toJSON', function () {
it('toJSON', function () {
assert.deepEqual(new Complex(2, 4).toJSON(), {'@type': 'Complex', re: 2, im: 4});
assert.deepEqual(new Complex(3, 0).toJSON(), {'@type': 'Complex', re: 3, im: 0});
});
it('should create a complex number from a JSON object', function () {
it('fromJSON', function () {
var c1 = Complex.fromJSON({re: 2, im: 4});
assert.ok(c1 instanceof Complex);
assert.strictEqual(c1.re, 2);

View File

@ -85,6 +85,26 @@ describe('Index', function () {
assert.equal(new Index([0,6,2]).toString(), '[0:2:6]');
});
it('toJSON', function () {
assert.deepEqual(new Index([0,10], 2).toJSON(),
{'@type': 'Index', ranges: [
new Range(0, 10, 1),
new Range(2, 3, 1)
]});
});
it('fromJSON', function () {
var json = {ranges: [
new Range(0, 10, 1),
new Range(2, 3, 1)
]};
var i1 = new Index([0,10], 2);
var i2 = Index.fromJSON(json);
assert.ok(i2 instanceof Index);
assert.deepEqual(i2, i1);
});
it('should get the range for a given dimension', function () {
var index = new Index(2, [0, 8, 2], [3,-1,-1]);

View File

@ -55,41 +55,25 @@ describe('matrix', function() {
});
it('toJSON', function() {
assert.deepEqual(new Matrix([[1,2],[3,new Complex(4,5)]]).toJSON(), {
assert.deepEqual(new Matrix([[1,2],[3,4]]).toJSON(), {
'@type': 'Matrix',
data: [[1,2],[3,{'@type': 'Complex', re: 4, im: 5}]]
data: [[1,2],[3,4]]
});
});
it('fromJSON', function() {
var json = {
'@type': 'Matrix',
data: [[1,2],[3,{'@type': 'Complex', re: 4, im: 5}]]
data: [[1,2],[3,4]]
};
var m = Matrix.fromJSON(json, math);
var m = Matrix.fromJSON(json);
assert.ok(m instanceof Matrix);
assert.deepEqual(m._size, [2, 2]);
assert.strictEqual(m._data[0][0], 1);
assert.strictEqual(m._data[0][1], 2);
assert.strictEqual(m._data[1][0], 3);
assert.ok(m._data[1][1] instanceof Complex);
assert.strictEqual(m._data[1][1].re, 4);
assert.strictEqual(m._data[1][1].im, 5);
});
it('fromJSON (2)', function() {
var json = [[1,2],[3,{'@type': 'Complex', re: 4, im: 5}]];
var m = Matrix.fromJSON(json, math);
assert.ok(m instanceof Matrix);
assert.deepEqual(m._size, [2, 2]);
assert.strictEqual(m._data[0][0], 1);
assert.strictEqual(m._data[0][1], 2);
assert.strictEqual(m._data[1][0], 3);
assert.ok(m._data[1][1] instanceof Complex);
assert.strictEqual(m._data[1][1].re, 4);
assert.strictEqual(m._data[1][1].im, 5);
assert.strictEqual(m._data[1][1], 4);
});
it('format', function() {

View File

@ -246,4 +246,23 @@ describe('range', function() {
});
});
it('toJSON', function () {
assert.deepEqual(new Range(2, 4).toJSON(), {'@type': 'Range', start: 2, end: 4, step: 1});
assert.deepEqual(new Range(0, 10, 2).toJSON(), {'@type': 'Range', start: 0, end: 10, step: 2});
});
it('fromJSON', function () {
var r1 = Range.fromJSON({start: 2, end: 4});
assert.ok(r1 instanceof Range);
assert.strictEqual(r1.start, 2);
assert.strictEqual(r1.end, 4);
assert.strictEqual(r1.step, 1);
var r2 = Range.fromJSON({start: 0, end: 10, step: 2});
assert.ok(r2 instanceof Range);
assert.strictEqual(r2.start, 0);
assert.strictEqual(r2.end, 10);
assert.strictEqual(r2.step, 2);
});
});

View File

@ -30,4 +30,18 @@ describe('ResultSet', function () {
assert.deepEqual(r.toString(), '[1, 2, 3, 4 + 5i]');
});
it('toJSON', function () {
var r = new ResultSet([1,2,3]);
var json = {"@type":"ResultSet","entries":[1,2,3]};
assert.deepEqual(r.toJSON(), json);
});
it('fromJSON', function () {
var r1 = new ResultSet([1,2,3]);
var json = {"@type":"ResultSet","entries":[1,2,3]};
var r2 = ResultSet.fromJSON(json);
assert(r2 instanceof ResultSet);
assert.deepEqual(r2, r1);
});
});

View File

@ -244,6 +244,29 @@ describe('unit', function() {
});
describe('json', function () {
it('toJSON', function () {
assert.deepEqual(new Unit(5, 'cm').toJSON(),
{'@type': 'Unit', value: 5, unit: 'cm', fixPrefix: false});
assert.deepEqual(new Unit(5, 'cm').to('mm').toJSON(),
{'@type': 'Unit', value: 50, unit: 'mm', fixPrefix: true});
});
it('fromJSON', function () {
var u1 = new Unit(5, 'cm');
var u2 = Unit.fromJSON({'@type': 'Unit', value: 5, unit: 'cm', fixPrefix: false});
assert.ok(u2 instanceof Unit);
assert.deepEqual(u2, u1);
var u3 = new Unit(5, 'cm').to('mm');
var u4 = Unit.fromJSON({'@type': 'Unit', value: 50, unit: 'mm', fixPrefix: true});
assert.ok(u4 instanceof Unit);
assert.deepEqual(u4, u3);
});
});
describe('format', function () {
it('should format units with given precision', function() {