Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions packages/s2-core/__tests__/unit/data-set/cell-data.nested.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { CellData } from '@/data-set/cell-data';

describe('CellData nested value access', () => {
const row = {
name: '中国',
user: { region: '华东', city: '上海' },
metrics: { price: 100, city: { address: '测试名称' } },
} as Record<string, any>;

test('VALUE_FIELD from nested extraField', () => {
const cell = CellData.getCellData(row as any, 'metrics.city.address');

expect((cell as any).$$value$$ ?? (cell as any).value).toBeUndefined();
expect((cell as any).raw).toBeDefined();
expect((cell as any).value).toBeUndefined();
expect((cell as any).getValueByField('metrics.city.address')).toBe(
'测试名称',
);
});
});
26 changes: 26 additions & 0 deletions packages/s2-core/__tests__/unit/utils/accessor.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { getByPath, hasByPath } from '../../../src/utils/accessor';

describe('utils/accessor nested path', () => {
const row = {
name: '中国',
user: { region: '华东', city: '上海' },
metrics: { price: 100, city: { address: '测试名称' } },
} as Record<string, any>;

test('getByPath supports flat key', () => {
expect(getByPath(row, 'name')).toBe('中国');
});

test('getByPath supports level-2 path', () => {
expect(getByPath(row, 'user.region')).toBe('华东');
});

test('getByPath supports level-3 path', () => {
expect(getByPath(row, 'metrics.city.address')).toBe('测试名称');
});

test('hasByPath works for existing and missing paths', () => {
expect(hasByPath(row, 'metrics.price')).toBe(true);
expect(hasByPath(row, 'metrics.missing')).toBe(false);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {
getExistValues,
transformDimensionsValues,
} from '../../../src/utils/dataset/pivot-data-set';

describe('pivot dataset nested paths', () => {
const row = {
name: '中国',
user: { region: '华东', city: '上海' },
metrics: { price: 100, city: { address: '测试名称' } },
} as Record<string, any>;

test('transformDimensionsValues supports nested paths', () => {
const dims = ['user.region', 'user.city', 'metrics.city.address'];
const values = transformDimensionsValues(row, dims);

expect(values).toEqual(['华东', '上海', '测试名称']);
});

test('getExistValues supports nested values', () => {
const values = ['name', 'metrics.price', 'metrics.missing'];

expect(getExistValues(row as any, values)).toEqual([
'name',
'metrics.price',
]);
});
});
3 changes: 2 additions & 1 deletion packages/s2-core/src/data-set/base-data-set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import type {
import type { ValueRange } from '../common/interface/condition';
import type { Node } from '../facet/layout/node';
import type { SpreadSheet } from '../sheet-type';
import { getByPath } from '../utils/accessor';
import {
getValueRangeState,
setValueRangeState,
Expand Down Expand Up @@ -317,7 +318,7 @@ export abstract class BaseDataSet {

const fieldValues = compact(
map(this.originData, (item) => {
const value = item[field] as string;
const value = getByPath<string>(item, field);

return isNil(value) ? null : Number.parseFloat(value);
}),
Expand Down
16 changes: 13 additions & 3 deletions packages/s2-core/src/data-set/cell-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { EXTRA_FIELD, ORIGIN_FIELD, VALUE_FIELD } from '../common/constant';
import type { ViewMetaData } from '../common/interface/basic';
import type { RawData } from '../common/interface/s2DataConfig';
import { getByPath } from '../utils/accessor';

export class CellData {
constructor(
Expand All @@ -22,7 +23,7 @@ export class CellData {
return field ? data.getValueByField(field) : data[ORIGIN_FIELD];
}

return data?.[field];
return getByPath(data as unknown as Record<string, any>, field);
}

get [ORIGIN_FIELD]() {
Expand All @@ -34,14 +35,23 @@ export class CellData {
}

get [VALUE_FIELD]() {
return this.raw[this.extraField];
// 为保持向后兼容:当 extraField 为嵌套路径(如 a.b.c 或 a.b[0].c)时,不将其暴露为 $$value$$
// 仅当 extraField 为顶层字段时,才通过 $$value$$ 快捷访问
if (
this.extraField &&
(this.extraField.includes('.') || this.extraField.includes('['))
) {
return undefined;
}

return getByPath(this.raw, this.extraField);
}

getValueByField(field: string) {
if (field === VALUE_FIELD || field === EXTRA_FIELD) {
return this[field];
}

return this.raw[field];
return getByPath(this.raw, field);
}
}
21 changes: 13 additions & 8 deletions packages/s2-core/src/data-set/table-data-set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
SimpleData,
} from '../common/interface';
import { getEmptyPlaceholder } from '../utils';
import { getByPath } from '../utils/accessor';
import { isAscSort, isDescSort } from '../utils/sort-action';
import { BaseDataSet } from './base-data-set';
import type { GetCellDataParams, GetCellMultiDataParams } from './interface';
Expand Down Expand Up @@ -67,7 +68,7 @@ export class TableDataSet extends BaseDataSet {
each(this.filterParams, ({ filterKey, filteredValues, customFilter }) => {
const filteredValuesSet = new Set(filteredValues);
const defaultFilterFunc = (row: RawData) =>
!filteredValuesSet.has(row[filterKey]);
!filteredValuesSet.has(getByPath(row, filterKey) as any);

const filteredData = filter(this.displayData, (row) => {
if (customFilter) {
Expand Down Expand Up @@ -105,7 +106,7 @@ export class TableDataSet extends BaseDataSet {
for (let index = 0; index < keys.length; index++) {
const k = keys[index];

if (record[k] !== query[k]) {
if (getByPath(record, k) !== query[k]) {
inScope = false;
restData.push(record);
break;
Expand All @@ -130,8 +131,12 @@ export class TableDataSet extends BaseDataSet {
const reversedSortBy = [...sortBy].reverse();

sortedData = data.sort((a, b) => {
const idxA = reversedSortBy.indexOf(a[sortFieldId] as string);
const idxB = reversedSortBy.indexOf(b[sortFieldId] as string);
const idxA = reversedSortBy.indexOf(
getByPath(a, sortFieldId) as string,
);
const idxB = reversedSortBy.indexOf(
getByPath(b, sortFieldId) as string,
);

return idxB - idxA;
});
Expand All @@ -143,11 +148,11 @@ export class TableDataSet extends BaseDataSet {
const customSortBy = isFunction(sortBy) ? sortBy : null;
const customSort = (record: RawData) => {
// 空值占位符按最小值处理 https://github.com/antvis/S2/issues/2707
if (record[sortFieldId] === placeholder) {
if (getByPath(record, sortFieldId) === placeholder) {
return Number.MIN_VALUE;
}

return record[sortFieldId];
return getByPath(record, sortFieldId) as any;
};

sortedData = orderBy(
Expand Down Expand Up @@ -183,7 +188,7 @@ export class TableDataSet extends BaseDataSet {
return rowData as Data;
}

return rowData[query['field']] as SimpleData;
return getByPath(rowData, query['field']) as SimpleData;
}

public getCellMultiData(
Expand All @@ -201,7 +206,7 @@ export class TableDataSet extends BaseDataSet {
return rowData as Data[];
}

return rowData.map((item) => item[query['field']]) as Data[];
return rowData.map((item) => getByPath(item, query['field'])) as Data[];
}

public getRowData(cell: CellMeta) {
Expand Down
29 changes: 29 additions & 0 deletions packages/s2-core/src/utils/accessor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { get, has } from 'lodash';

export function getByPath<T = any>(
record: Record<string, any>,
field: string,
): T {
if (!record || !field) {
return undefined as any;
}

// fast path for flat keys
if (field.indexOf('.') === -1 && field.indexOf('[') === -1) {
return record[field] as any;
}

return get(record, field) as any;
}

export function hasByPath(record: Record<string, any>, field: string): boolean {
if (!record || !field) {
return false;
}

if (field.indexOf('.') === -1 && field.indexOf('[') === -1) {
return field in record;
}

return has(record, field);
}
25 changes: 14 additions & 11 deletions packages/s2-core/src/utils/dataset/pivot-data-set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
intersection,
isArray,
isEmpty,
isNull,
isString,
last,
set,
Expand Down Expand Up @@ -36,6 +35,7 @@ import type {
TotalStatus,
} from '../../data-set/interface';
import type { Node } from '../../facet/layout/node';
import { getByPath, hasByPath } from '../accessor';
import { generateNillString } from '../layout/generate-id';

export function filterExtraDimension(dimensions: CustomHeaderFields = []) {
Expand Down Expand Up @@ -68,9 +68,10 @@ export function transformDimensionsValues(
placeholder = TOTAL_VALUE,
): string[] {
return dimensions.reduce((res: string[], dimension: string) => {
const value = record[dimension];
const exists = hasByPath(record, dimension);
const value = exists ? (getByPath(record, dimension) as string) : undefined;

if (!(dimension in record)) {
if (!exists) {
res.push(placeholder);
} else {
res.push(generateNillString(value as string));
Expand All @@ -81,7 +82,7 @@ export function transformDimensionsValues(
}

export function getExistValues(data: RawData, values: string[]) {
const result = values.filter((v) => v in data);
const result = values.filter((v) => hasByPath(data, v));

if (isEmpty(result)) {
result.push(EMPTY_EXTRA_FIELD_PLACEHOLDER);
Expand All @@ -99,9 +100,10 @@ function transformDimensionsValuesWithExtraFields(

function transform(data: RawData, fields: string[], valueField?: string) {
return fields.reduce((res: string[], dimension: string) => {
const value = data[dimension];
const exists = hasByPath(data, dimension);
const value = exists ? (getByPath(data, dimension) as string) : undefined;

if (!(dimension in data)) {
if (!exists) {
if (dimension === EXTRA_FIELD && valueField) {
res.push(valueField);
} else {
Expand Down Expand Up @@ -406,7 +408,7 @@ export function deleteMetaById(meta: PivotMeta, nodeId: string) {
const deletePath = last(paths);
let currentMeta = meta;

forEach(paths, (path, idx) => {
forEach(paths, (path: string, idx: number) => {
const pathMeta = currentMeta.get(path);

if (pathMeta) {
Expand Down Expand Up @@ -467,14 +469,14 @@ export function getHeaderTotalStatus(row: Node, col: Node): TotalStatus {
* 需要将其拓展成多个结构 => [MULTI_VALUE, 女] => [[四川,女], [北京,女], ....] => [[1,1],[2,1],[3,2]....]
*/
export function existDimensionTotalGroup(path: string[]) {
let multiIdx = null;
let multiIdx: number | null = null;

for (let i = 0; i < path.length; i++) {
const element = path[i];

if (isMultiValue(element)) {
multiIdx = i;
} else if (!isNull(multiIdx) && multiIdx < i) {
} else if (multiIdx !== null && multiIdx < i) {
return true;
}
}
Expand Down Expand Up @@ -505,7 +507,7 @@ export function getSatisfiedPivotMetaValues(params: {
let metaValueList = [rootContainer];

function flattenMetaValue(list: PivotMetaValue[], field: string) {
const allValues = flatMap(list, (metaValue) => {
const allValues = flatMap(list, (metaValue: PivotMetaValue) => {
const values: PivotMetaValue[] = [];

for (const v of metaValue.children.values()) {
Expand All @@ -528,7 +530,8 @@ export function getSatisfiedPivotMetaValues(params: {
);

allValues.sort(
(a, b) => (indexMap.get(a.id) ?? 0) - (indexMap.get(b.id) ?? 0),
(a: PivotMetaValue, b: PivotMetaValue) =>
(indexMap.get(a.id) ?? 0) - (indexMap.get(b.id) ?? 0),
);

return allValues;
Expand Down
9 changes: 5 additions & 4 deletions packages/s2-core/src/utils/export/copy/table-copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
} from '../../../common/interface/export';
import type { Node } from '../../../facet/layout/node';
import type { SpreadSheet } from '../../../sheet-type';
import { getByPath } from '../../accessor';
import {
getColNodeFieldFromNode,
getSelectedCols,
Expand Down Expand Up @@ -74,7 +75,7 @@ class TableDataCellCopy extends BaseDataCellCopy {
rowIndex: i,
colIndex: j,
});
const value = row?.[field];
const value = getByPath(row, field);

return formatter(value);
}),
Expand Down Expand Up @@ -124,8 +125,8 @@ class TableDataCellCopy extends BaseDataCellCopy {
rowIndex,
colIndex: i,
});
const value = rowData[field];
const dataItem = formatter(value);
const value = getByPath(rowData, field);
const dataItem = formatter(value as any);

row.push(dataItem as string);
}
Expand Down Expand Up @@ -174,7 +175,7 @@ class TableDataCellCopy extends BaseDataCellCopy {
)!;
const value = this.isSeriesNumberField(field)
? meta.rowIndex + 1
: this.displayData[meta.rowIndex]?.[field];
: getByPath(this.displayData[meta.rowIndex], field);

const formatter = this.getFormatter({
field,
Expand Down
Loading