feat(Utils): dataSerialize, dataDeserialize, numberHasDecimals, numberIs64Bit

This commit is contained in:
Nathan Walker
2022-05-15 11:05:23 -07:00
parent 2250c7fc6a
commit cab59473f3
7 changed files with 264 additions and 59 deletions

View File

@ -105,8 +105,8 @@ export type { InstrumentationMode, TimerInfo } from './profiling';
export { encoding } from './text'; export { encoding } from './text';
export * from './trace'; export * from './trace';
export * from './ui'; export * from './ui';
import { GC, isFontIconURI, isDataURI, isFileOrResourcePath, executeOnMainThread, mainThreadify, isMainThread, dispatchToMainThread, executeOnUIThread, releaseNativeObject, getModuleName, openFile, openUrl, isRealDevice, layout, ad as androidUtils, iOSNativeHelper as iosUtils, Source, escapeRegexSymbols, convertString, dismissSoftInput, queueMacrotask, queueGC, throttle, debounce } from './utils'; import { GC, isFontIconURI, isDataURI, isFileOrResourcePath, executeOnMainThread, mainThreadify, isMainThread, dispatchToMainThread, executeOnUIThread, releaseNativeObject, getModuleName, openFile, openUrl, isRealDevice, layout, ad as androidUtils, iOSNativeHelper as iosUtils, Source, escapeRegexSymbols, convertString, dismissSoftInput, queueMacrotask, queueGC, throttle, debounce, dataSerialize, dataDeserialize } from './utils';
import { ClassInfo, getClass, getBaseClasses, getClassInfo, isBoolean, isDefined, isFunction, isNullOrUndefined, isNumber, isObject, isString, isUndefined, toUIString, verifyCallback } from './utils/types'; import { ClassInfo, getClass, getBaseClasses, getClassInfo, isBoolean, isDefined, isFunction, isNullOrUndefined, isNumber, isObject, isString, isUndefined, toUIString, verifyCallback, numberHasDecimals, numberIs64Bit } from './utils/types';
export declare const Utils: { export declare const Utils: {
GC: typeof GC; GC: typeof GC;
RESOURCE_PREFIX: string; RESOURCE_PREFIX: string;
@ -134,6 +134,10 @@ export declare const Utils: {
android: typeof androidUtils; android: typeof androidUtils;
ad: typeof androidUtils; ad: typeof androidUtils;
ios: typeof iosUtils; ios: typeof iosUtils;
dataSerialize: typeof dataSerialize;
dataDeserialize: typeof dataDeserialize;
numberHasDecimals: typeof numberHasDecimals;
numberIs64Bit: typeof numberIs64Bit;
setTimeout: typeof setTimeout; setTimeout: typeof setTimeout;
setInterval: typeof setInterval; setInterval: typeof setInterval;
clearInterval: typeof clearInterval; clearInterval: typeof clearInterval;

View File

@ -137,8 +137,8 @@ export * from './trace';
export * from './ui'; export * from './ui';
import { GC, isFontIconURI, isDataURI, isFileOrResourcePath, executeOnMainThread, mainThreadify, isMainThread, dispatchToMainThread, executeOnUIThread, queueMacrotask, queueGC, debounce, throttle, releaseNativeObject, getModuleName, openFile, openUrl, isRealDevice, layout, ad as androidUtils, iOSNativeHelper as iosUtils, Source, RESOURCE_PREFIX, FILE_PREFIX, escapeRegexSymbols, convertString, dismissSoftInput } from './utils'; import { GC, isFontIconURI, isDataURI, isFileOrResourcePath, executeOnMainThread, mainThreadify, isMainThread, dispatchToMainThread, executeOnUIThread, queueMacrotask, queueGC, debounce, throttle, releaseNativeObject, getModuleName, openFile, openUrl, isRealDevice, layout, ad as androidUtils, iOSNativeHelper as iosUtils, Source, RESOURCE_PREFIX, FILE_PREFIX, escapeRegexSymbols, convertString, dismissSoftInput, dataDeserialize, dataSerialize } from './utils';
import { ClassInfo, getClass, getBaseClasses, getClassInfo, isBoolean, isDefined, isFunction, isNullOrUndefined, isNumber, isObject, isString, isUndefined, toUIString, verifyCallback } from './utils/types'; import { ClassInfo, getClass, getBaseClasses, getClassInfo, isBoolean, isDefined, isFunction, isNullOrUndefined, isNumber, isObject, isString, isUndefined, toUIString, verifyCallback, numberHasDecimals, numberIs64Bit } from './utils/types';
export const Utils = { export const Utils = {
GC, GC,
@ -170,6 +170,10 @@ export const Utils = {
// legacy (a lot of plugins use the shorthand "ad" Utils.ad instead of Utils.android) // legacy (a lot of plugins use the shorthand "ad" Utils.ad instead of Utils.android)
ad: androidUtils, ad: androidUtils,
ios: iosUtils, ios: iosUtils,
dataSerialize,
dataDeserialize,
numberHasDecimals,
numberIs64Bit,
setTimeout, setTimeout,
setInterval, setInterval,
clearInterval, clearInterval,

View File

@ -1,5 +1,143 @@
import { getNativeApplication, android as androidApp } from '../application'; import { getNativeApplication, android as androidApp } from '../application';
import { Trace } from '../trace'; import { Trace } from '../trace';
import { numberHasDecimals, numberIs64Bit } from './types';
export function dataDeserialize(nativeData?: any) {
if (nativeData === null || typeof nativeData !== 'object') {
return nativeData;
}
let store;
switch (nativeData.getClass().getName()) {
case 'java.lang.String': {
return String(nativeData);
}
case 'java.lang.Boolean': {
return String(nativeData) === 'true';
}
case 'java.lang.Float':
case 'java.lang.Integer':
case 'java.lang.Long':
case 'java.lang.Double':
case 'java.lang.Short': {
return Number(nativeData);
}
case 'org.json.JSONArray': {
store = [];
for (let j = 0; j < nativeData.length(); j++) {
store[j] = dataDeserialize(nativeData.get(j));
}
break;
}
case 'org.json.JSONObject': {
store = {};
let i = nativeData.keys();
while (i.hasNext()) {
let key = i.next();
store[key] = dataDeserialize(nativeData.get(key));
}
break;
}
case 'androidx.collection.SimpleArrayMap': {
const count = nativeData.size();
for (let l = 0; l < count; l++) {
const key = nativeData.keyAt(l);
store[key] = dataDeserialize(nativeData.get(key));
}
break;
}
case 'androidx.collection.ArrayMap':
case 'android.os.Bundle':
case 'java.util.HashMap':
case 'java.util.Map': {
store = {};
const keys = nativeData.keySet().toArray();
for (let k = 0; k < keys.length; k++) {
const key = keys[k];
store[key] = dataDeserialize(nativeData.get(key));
}
break;
}
default:
if (typeof nativeData === 'object' && nativeData instanceof java.util.List) {
const array = [];
const size = nativeData.size();
for (let i = 0, n = size; i < n; i++) {
array[i] = dataDeserialize(nativeData.get(i));
}
store = array;
} else {
store = null;
}
break;
}
return store;
}
export function dataSerialize(data?: any, wrapPrimitives?: boolean) {
let store;
switch (typeof data) {
case 'string':
case 'boolean': {
if (wrapPrimitives) {
if (typeof data === 'string') {
return new java.lang.String(data);
}
return new java.lang.Boolean(data);
}
return data;
}
case 'number': {
const hasDecimals = numberHasDecimals(data);
if (numberIs64Bit(data)) {
if (hasDecimals) {
return java.lang.Double.valueOf(data);
} else {
return java.lang.Long.valueOf(data);
}
} else {
if (hasDecimals) {
return java.lang.Float.valueOf(data);
} else {
return java.lang.Integer.valueOf(data);
}
}
}
case 'object': {
if (!data) {
return null;
}
if (data instanceof Date) {
return new java.util.Date(data.getTime());
}
if (Array.isArray(data)) {
store = new java.util.ArrayList();
data.forEach((item) => store.add(dataSerialize(item, wrapPrimitives)));
return store;
}
if (data.native) {
return data.native;
}
store = new java.util.HashMap();
Object.keys(data).forEach((key) => store.put(key, dataSerialize(data[key], wrapPrimitives)));
return store;
}
default:
return null;
}
}
// We are using "ad" here to avoid namespace collision with the global android object // We are using "ad" here to avoid namespace collision with the global android object
export namespace ad { export namespace ad {

View File

@ -1,3 +1,14 @@
/**
* Data serialization from JS > Native
* @param wrapPrimitives Optionally wrap primitive types (Some APIs may require this)
*/
export function dataSerialize(data?: any, wrapPrimitives?: boolean): any;
/**
* Data deserialization from Native > JS
* @param nativeData Native platform data
*/
export function dataDeserialize(nativeData?: any): any;
/** /**
* Module with android specific utilities. * Module with android specific utilities.
*/ */

View File

@ -1,5 +1,5 @@
import { Trace } from '../trace'; import { Trace } from '../trace';
import { getClass, isNullOrUndefined } from './types'; import { getClass, isNullOrUndefined, numberHasDecimals, numberIs64Bit } from './types';
declare let UIImagePickerControllerSourceType: any; declare let UIImagePickerControllerSourceType: any;
@ -25,35 +25,6 @@ function openFileAtRootModule(filePath: string): boolean {
return false; return false;
} }
export namespace iOSNativeHelper {
// TODO: remove for NativeScript 7.0
export function getter<T>(_this: any, property: T | { (): T }): T {
console.log('utils.ios.getter() is deprecated; use the respective native property instead');
if (typeof property === 'function') {
return (<{ (): T }>property).call(_this);
} else {
return <T>property;
}
}
export namespace collections {
export function jsArrayToNSArray<T>(str: T[]): NSArray<T> {
return NSArray.arrayWithArray(str);
}
export function nsArrayToJSArray<T>(a: NSArray<T>): Array<T> {
const arr = [];
if (a !== undefined) {
const count = a.count;
for (let i = 0; i < count; i++) {
arr.push(a.objectAtIndex(i));
}
}
return arr;
}
}
export function dataDeserialize(nativeData?: any) { export function dataDeserialize(nativeData?: any) {
if (isNullOrUndefined(nativeData)) { if (isNullOrUndefined(nativeData)) {
// some native values will already be js null values // some native values will already be js null values
@ -87,24 +58,81 @@ export namespace iOSNativeHelper {
} }
} }
export function dataSerialize(data?: any) { export function dataSerialize(data: any, wrapPrimitives: boolean = false) {
switch (typeof data) { switch (typeof data) {
case 'number':
case 'string': case 'string':
case 'boolean': case 'boolean': {
return data; return data;
case 'object': }
if (Array.isArray(data)) { case 'number': {
return NSArray.arrayWithArray(<any>data.map(dataSerialize)); const hasDecimals = numberHasDecimals(data);
if (numberIs64Bit(data)) {
if (hasDecimals) {
return NSNumber.alloc().initWithDouble(data);
} else {
return NSNumber.alloc().initWithLongLong(data);
}
} else {
if (hasDecimals) {
return NSNumber.alloc().initWithFloat(data);
} else {
return data;
}
}
} }
let obj = {}; case 'object': {
for (let key of Object.keys(data)) { if (data instanceof Date) {
obj[key] = dataSerialize(data[key]); return NSDate.dateWithTimeIntervalSince1970(data.getTime() / 1000);
} }
return NSDictionary.dictionaryWithDictionary(<any>obj);
if (!data) {
return null;
}
if (Array.isArray(data)) {
return NSArray.arrayWithArray((<any>data).map(dataSerialize));
}
let node = {} as any;
Object.keys(data).forEach(function (key) {
let value = data[key];
node[key] = dataSerialize(value, wrapPrimitives);
});
return NSDictionary.dictionaryWithDictionary(node);
}
default: default:
return NSNull.new(); return null;
}
}
export namespace iOSNativeHelper {
// TODO: remove for NativeScript 7.0
export function getter<T>(_this: any, property: T | { (): T }): T {
console.log('utils.ios.getter() is deprecated; use the respective native property instead');
if (typeof property === 'function') {
return (<{ (): T }>property).call(_this);
} else {
return <T>property;
}
}
export namespace collections {
export function jsArrayToNSArray<T>(str: T[]): NSArray<T> {
return NSArray.arrayWithArray(str);
}
export function nsArrayToJSArray<T>(a: NSArray<T>): Array<T> {
const arr = [];
if (a !== undefined) {
const count = a.count;
for (let i = 0; i < count; i++) {
arr.push(a.objectAtIndex(i));
}
}
return arr;
} }
} }

View File

@ -61,6 +61,18 @@ export function isNullOrUndefined(value: any): boolean;
*/ */
export function verifyCallback(value: any): void; export function verifyCallback(value: any): void;
/**
* Checks if the number has decimals
* @param value Number to check
*/
export function numberHasDecimals(value: number): boolean;
/**
* Checks if the number is 64 bit
* @param value Number to check
*/
export function numberIs64Bit(value: number): boolean;
/** /**
* A function that gets the class name of an object. * A function that gets the class name of an object.
* @param object The object. * @param object The object.

View File

@ -44,6 +44,14 @@ export function verifyCallback(value: any) {
} }
} }
export function numberHasDecimals(value: number): boolean {
return !(value % 1 === 0);
}
export function numberIs64Bit(value: number): boolean {
return value < -Math.pow(2, 31) + 1 || value > Math.pow(2, 31) - 1;
}
const classInfosMap = new Map<Function, ClassInfo>(); const classInfosMap = new Map<Function, ClassInfo>();
// ES3-5 type classes are "function blah()", new ES6+ classes can be "class blah" // ES3-5 type classes are "function blah()", new ES6+ classes can be "class blah"