-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
162 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { ListWrapper, RawList, WrappedList } from './ListWrapper' | ||
|
||
describe('ListWrapper', () => { | ||
interface Item { | ||
id: number | ||
name: string | ||
} | ||
|
||
const rawList: RawList<Item> = [ | ||
{ id: 1, name: 'Jay' }, | ||
{ id: 2, name: 'Jay2' }, | ||
{ id: 3, name: 'Jay3' }, | ||
{ id: 4, name: 'Jay4' }, | ||
] | ||
const wrappedList: WrappedList<Item> = new ListWrapper(rawList).wrap() | ||
const unwrappedList: RawList<Item> = new ListWrapper(wrappedList).unwrap() | ||
|
||
test('基础表现正常', () => { | ||
expect(unwrappedList).toEqual(rawList) | ||
expect(ListWrapper.unwrapIfNeeded(wrappedList)).toEqual(rawList) | ||
expect(ListWrapper.unwrapIfNeeded([wrappedList])).toEqual([wrappedList]) | ||
expect(ListWrapper.unwrapIfNeeded(1)).toEqual(1) | ||
expect(ListWrapper.unwrapIfNeeded([])).toEqual([]) | ||
}) | ||
|
||
test('支持递归', () => { | ||
expect( | ||
ListWrapper.unwrapIfNeeded({ | ||
list: wrappedList, | ||
}), | ||
).toEqual({ list: rawList }) | ||
expect( | ||
ListWrapper.unwrapIfNeeded({ list: { list: wrappedList } }), | ||
).toEqual({ list: { list: rawList } }) | ||
expect( | ||
ListWrapper.unwrapIfNeeded({ | ||
list: { list: { list: wrappedList } }, | ||
}), | ||
).toEqual({ list: { list: { list: wrappedList } } }) | ||
expect( | ||
ListWrapper.unwrapIfNeeded({ list: { list: { list: wrappedList } } }, 3), | ||
).toEqual({ list: { list: { list: rawList } } }) | ||
}) | ||
|
||
test('bug: 空数据正常', () => { | ||
expect(new ListWrapper(new ListWrapper([]).wrap()).unwrap()).toEqual([]) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import { base64UrlDecode, base64UrlEncode } from './base64' | ||
import { isPlainObject, mapValues, range, shuffle } from 'lodash-uni' | ||
|
||
export type RawList<TItem = any> = TItem[] | ||
|
||
export interface WrappedList<TItem = any> { | ||
readonly _k: Array<keyof TItem> | ||
readonly _v: Array<Array<TItem[keyof TItem]>> | ||
readonly _s: string | ||
} | ||
|
||
/** | ||
* 列表打包器。 | ||
*/ | ||
export class ListWrapper<TItem> { | ||
constructor(private list: RawList<TItem> | WrappedList<TItem>) {} | ||
|
||
/** | ||
* 如果是打包后的列表数据,则解包后返回,否则直接返回。如果是对象,则递归尝试解包。 | ||
* | ||
* @param value 数据 | ||
* @param depth 递归层级,默认:2 | ||
* @returns 返回结果数据 | ||
*/ | ||
static unwrapIfNeeded(value: any, depth = 2): any { | ||
if (isPlainObject(value)) { | ||
if ( | ||
(value as WrappedList)._k && | ||
(value as WrappedList)._v && | ||
(value as WrappedList)._s | ||
) { | ||
return new ListWrapper(value as any).unwrap() | ||
} | ||
if (depth > 0) { | ||
return mapValues(value, v => ListWrapper.unwrapIfNeeded(v, depth - 1)) | ||
} | ||
} | ||
return value | ||
} | ||
|
||
private static rot13(str: string) { | ||
return str.replace(/[a-z]/gi, char => { | ||
return String.fromCharCode( | ||
char.charCodeAt(0) + (char.toLowerCase() < 'n' ? 13 : -13), | ||
) | ||
}) | ||
} | ||
|
||
private static encodeValueIndexes(indexes: number[]) { | ||
return ListWrapper.rot13( | ||
base64UrlEncode(`${new Date().getTime()}.${indexes.join('.')}`), | ||
) | ||
} | ||
|
||
private static decodeValueIndexes(value: string) { | ||
return base64UrlDecode(ListWrapper.rot13(value)) | ||
.split('.') | ||
.slice(1) | ||
.map(Number) | ||
} | ||
|
||
/** | ||
* 打包结构化列表数据。 | ||
* | ||
* @returns 返回打包后的结构化列表数据 | ||
*/ | ||
wrap(): WrappedList<TItem> { | ||
const rawList: RawList<TItem> = this.list as any | ||
const keys: Array<keyof TItem> = rawList.length | ||
? (Object.keys(rawList[0]) as any) | ||
: [] | ||
const valueIndexes: number[] = shuffle(range(0, keys.length)) | ||
const values = [] | ||
for (const structuredItem of rawList) { | ||
const item = [] | ||
for (let i = 0, len = valueIndexes.length; i < len; i++) { | ||
item[valueIndexes[i]] = structuredItem[keys[i]] | ||
} | ||
values.push(item) | ||
} | ||
return { | ||
_k: keys, | ||
_v: values, | ||
_s: ListWrapper.encodeValueIndexes(valueIndexes), | ||
} | ||
} | ||
|
||
/** | ||
* 返回结果同 `wrap()`,不过类型是原列表的类型。 | ||
*/ | ||
wrapAsRawType(): RawList<TItem> { | ||
return this.wrap() as any | ||
} | ||
|
||
/** | ||
* 解包结构化列表数据。 | ||
* | ||
* @returns 返回解包后的结构化列表数据 | ||
*/ | ||
unwrap(): RawList<TItem> { | ||
const wrappedList: WrappedList<TItem> = this.list as any | ||
const rawList: TItem[] = [] | ||
const valueIndexes = ListWrapper.decodeValueIndexes(wrappedList._s) | ||
for (const values of wrappedList._v) { | ||
const item: TItem = {} as any | ||
for (let i = 0, len = valueIndexes.length; i < len; i++) { | ||
item[wrappedList._k[i]] = values[valueIndexes[i]] | ||
} | ||
rawList.push(item) | ||
} | ||
return rawList | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters