blob: aef81f47d7154fc56c6f87c5ce6edae1c46b54c0 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/**
* @fileoverview Converts native JavaScript values to and from JSValue
* The outputted JSValues are not necessarily canonical, nor in the same form.
* For example, a Set turns into a list of its keys, and Map and Object become
* a list of key-value pairs.
* This file should not be exported at the top-level; it is meant to be used by
* canonicalize only. It is unit-tested separately.
* @private
*/
var typeUtil = require('./type-util.js');
var types = require('./types.js');
var util = require('./util.js');
require('./es6-shim');
module.exports = {
fromNative: convertFromNative,
toNative: convertToNative
};
// There is only a single JSValueConstructor.
// In order to avoid any cyclical dependencies, this constructor is obtained
// from the registry with delayed dependency injection.
// TODO(alexfandrianto): Can this be obtained from a VDL file that defines the
// JSValue? A potential issue is that VDL-generated files require 'vom', and
// this is the 'vom' library.
var JSValueConstructor = null;
function getJSValueConstructor() {
if (JSValueConstructor === null) {
var Registry = require('./registry.js');
JSValueConstructor = Registry.lookupOrCreateConstructor(types.JSVALUE);
}
return JSValueConstructor;
}
/**
* Convert the given raw value into the proper JSValue form.
* Note: Skips typed values, so it will not convert any native values there.
* Excluding undefined, raw values satisfy the following equality:
* convertToNative(convertFromNative(val)) === val
* @private
* @param {*} val The value to be molded into a JSValue.
* @return {*} The JSValue.
*/
function convertFromNative(val) {
// No need to convert if val is already a JSValue or typed object.
// Note: In this case, val is NOT a new reference.
if (typeUtil.isTyped(val)) {
return val;
}
// Associate the JSValue prototype with the returned object.
// Avoids using 'new JSValue(...)' because that would call canonicalize.
var JSValue = getJSValueConstructor();
var ret = Object.create(JSValue.prototype);
if (val === undefined || val === null) {
ret.null = {}; // must be the 'empty struct', but any value will do.
} else if (typeof val === 'boolean') {
ret.boolean = val;
} else if (typeof val === 'number') {
ret.number = val;
} else if (typeof val === 'string') {
ret.string = val;
} else if (typeof val !== 'object') {
// From here on, only objects can convert to JSValue.
throw new TypeError('Cannot convert a ' + (typeof val) + ' to JSValue');
} else if (val instanceof Uint8Array) {
ret.bytes = new Uint8Array(val);
} else if (Array.isArray(val)) {
ret.list = val.map(function(elem) {
return convertFromNative(elem);
});
} else if (val instanceof Set) {
// Set: Return a []JSValue
var keys = [];
val.forEach(function(key) {
keys.push(convertFromNative(key));
});
ret.set = keys;
} else if (val instanceof Map) {
// Map: Return []{key, value pairs}
var keyVals = [];
val.forEach(function(elem, key) {
keyVals.push({
'key': convertFromNative(key),
'value': convertFromNative(elem)
});
});
ret.map = keyVals;
} else {
// defaults to... Object: Return []{string key, value pairs}
// Note: Ignores 'private' fields: keys that start with '_'
ret.object = Object.keys(val).filter(util.isExportedStructField).map(
function(key) {
return {
'key': key,
'value': convertFromNative(val[key])
};
}
);
}
return ret;
}
/**
* Convert the given JSValue into the proper raw value.
* Note: Skips conversion of non-JS values.
* Excluding undefined, raw values satisfy the following equality:
* convertToNative(convertFromNative(val)) === val
* @private
* @param{JSValue} jsval The JSValue to be restored into raw form.
* @return The raw value
*/
function convertToNative(jsval) {
// No need to convert if jsval lacks type or isn't of type JSValue.
if (!typeUtil.isTyped(jsval) || !types.JSVALUE.equals(jsval._type)) {
return jsval;
}
if (jsval === undefined) {
return null;
}
// jsval is in the Union format. Extract its value, ignoring keys associated
// with undefined values.
var jsvalKey = util.getFirstDefinedPropertyKey(jsval);
if (jsvalKey === undefined) {
throw new Error('could not convert from JSValue. given: ' +
JSON.stringify(jsval));
}
var jsvalElem = jsval[jsvalKey];
return convertToNativeInternal(jsvalKey, jsvalElem);
}
// Based on the key and internal JSValue, return the raw value.
function convertToNativeInternal(jsvalKey, jsvalElem) {
switch(jsvalKey) {
case 'null':
return null;
case 'boolean':
case 'number':
case 'string':
case 'bytes':
return jsvalElem;
case 'list':
var list = new Array(jsvalElem.length);
for (var i = 0; i < jsvalElem.length; i++) {
list[i] = convertToNative(jsvalElem[i]);
}
return list;
case 'set':
var set = new Set();
jsvalElem.forEach(function(j) {
set.add(convertToNative(j));
});
return set;
case 'map':
var map = new Map();
jsvalElem.forEach(function(j) {
map.set(
convertToNative(j.key),
convertToNative(j.value)
);
});
return map;
case 'object':
var object = {};
jsvalElem.forEach(function(j) {
object[j.key] = convertToNative(j.value);
});
return object;
default:
throw new Error('unknown JSValue key ' + jsvalKey + ' with value ' +
jsvalElem);
}
}