import isError from 'lodash-es/isError.js'
import get from 'lodash-es/get.js'
import isbol from './isbol.mjs'
import isobj from './isobj.mjs'
import isobj0 from './isobj0.mjs'
import isestr from './isestr.mjs'
import isarr from './isarr.mjs'
import isarr0 from './isarr0.mjs'
import isu8arr from './isu8arr.mjs'
import isu16arr from './isu16arr.mjs'
import isab from './isab.mjs'
//import treeObj from './treeObj.mjs'
/**
* 物件資料轉字串與Unit8Array,物件內可含Unit8Array數據,適用於大檔。
*
* 通過JSON序列化物件內非Unit8Array數據成為字串,另分拆Unit8Array數據出來回傳,兩者間通過指標關聯,主要為避免序列化大型Unit8Array數據造成效能或記憶體不足問題
*
* 因以JSON.stringify序列化,下列型別會靜默失真且不會拋錯,須由呼叫端自行避免:NaN與Infinity轉為null;值為undefined、函數或Symbol者該鍵會消失(若為唯一鍵則results為'{}');Map、Set、RegExp轉為{};Date轉為ISO字串;稀疏陣列之空洞補為null;超出Number精度之大整數會失精。另Error物件由本函數以toString轉為字串
*
* Unit Test: {@link https://github.com/yuda-lyu/wsemi/blob/master/test/obj2stru8arr.test.mjs Github}
* @memberOf wsemi
* @param {Object|Array} data 輸入物件或陣列資料
* @param {Object} [opt={}] 輸入設定物件,預設{}
* @param {Boolean} [opt.returnWithStateAndMsg=false] 輸入是否回傳含狀態與訊息物件布林值,若為true則回傳{ state, msg }物件,state為'success'或'error',msg於success時為回傳結果、於error時為錯誤訊息字串,預設false
* @returns {Object} 回傳物件,results欄位儲存物件內非序列化文字,binarys欄位儲存各Unit8Array數據;二進位數據於results內以'[BlazeForUint8Array]::<i>'一類標記代表,應用字串若恰與標記格式完整相同則會被前置'[BlazeForPreventEscape]'跳脫,供解碼端還原時區分;輸入非物件非陣列、空物件空陣列或序列化失敗(如含BigInt、循環參照)時回傳{ results: '', binarys: [] };若opt.returnWithStateAndMsg為true則回傳{ state, msg }物件
* @example
*
* let data = {
* a: 123,
* b: 45.67,
* c: 'l1-測試中文',
* d: {
* da: 123,
* db: 45.67,
* dc: 'l2-測試中文',
* dd: ['a', 'xyz', 321, 76.54],
* de: new Uint8Array([66, 97, 115]),
* },
* }
* let r = obj2stru8arr(data)
* console.log(r)
* // => {
* // results: '{"a":123,"b":45.67,"c":"l1-測試中文","d":{"da":123,"db":45.67,"dc":"l2-測試中文","dd":["a","xyz",321,76.54],"de":"[BlazeForUint8Array]::0"}}',
* // binarys: [ Uint8Array [ 66, 97, 115 ] ]
* // }
*
* console.log(obj2stru8arr({ id: 1n }, { returnWithStateAndMsg: true }))
* // => {
* // state: 'error',
* // msg: 'TypeError: Do not know how to serialize a BigInt'
* // }
*
*/
function obj2stru8arr(o, opt = {}) {
//returnWithStateAndMsg
let returnWithStateAndMsg = get(opt, 'returnWithStateAndMsg', null)
if (!isbol(returnWithStateAndMsg)) {
returnWithStateAndMsg = false
}
//retEmpty, 失敗時之空回傳值
let retEmpty = () => {
return {
results: '',
binarys: []
}
}
//retError
let retError = (msg) => {
if (returnWithStateAndMsg) {
return {
state: 'error',
msg,
}
}
else {
return retEmpty()
}
}
//check, 物件與陣列皆支援(obj2u8arr之輸入契約為物件或陣列, 解碼端stru8arr2obj亦原就能還原陣列)
if (!isobj(o) && !isarr(o)) {
return retError('invalid data, data is not an object or array')
}
if (isobj0(o) || isarr0(o)) {
return retError('invalid data, data is an empty object or empty array')
}
//reNeedEsc, 應用字串若與標記格式完整相同(或已是跳脫過之外觀), 須前置跳脫記號, 否則解碼端會誤判為二進位參照; 允許重複跳脫故以*涵蓋多層
let tagEsc = '[BlazeForPreventEscape]'
let reNeedEsc = /^(?:\[BlazeForPreventEscape\])*\[BlazeFor(?:Uint8Array|Uint16Array|ArrayBuffer)\]::\d+$/
let r = ''
let bs = []
try {
//分離u8a或u16a或ab出來
let i = -1
r = JSON.stringify(o, function(key, value) {
//console.log(key, value)
if (isu8arr(value)) {
i += 1
let id = `[BlazeForUint8Array]::${i}`
bs.push(value)
return id
}
else if (isu16arr(value)) {
i += 1
let id = `[BlazeForUint16Array]::${i}`
bs.push(value)
return id
}
else if (isab(value)) {
i += 1
let id = `[BlazeForArrayBuffer]::${i}`
bs.push(value)
return id
}
if (isError(value)) {
value = value.toString()
}
//跳脫, 以首字元'['(charCode 91)快篩, 避免對每個字串都跑regex
if (isestr(value) && value.charCodeAt(0) === 91 && reNeedEsc.test(value)) {
return tagEsc + value
}
return value
})
// //treeObj, 分離u8a或u16a出來
// let i = -1
// let t = treeObj(o, function(key, value) {
// //console.log(key, value)
// if (isu8arr(value)) {
// i += 1
// let id = `[BlazeForUint8Array]::${i}`
// bs.push(value)
// return id
// }
// else if (isu16arr(value)) {
// i += 1
// let id = `[BlazeForUint16Array]::${i}`
// bs.push(value)
// return id
// }
// return value
// })
// //stringify
// r = JSON.stringify(t)
}
catch (err) {
//JSON.stringify拋錯(如含BigInt、循環參照)時, bs可能已被推入部份數據, 故一律回乾淨之空值
return retError(err.toString())
}
if (returnWithStateAndMsg) {
return {
state: 'success',
msg: {
results: r,
binarys: bs
},
}
}
else {
return {
results: r,
binarys: bs
}
}
}
export default obj2stru8arr