diff --git a/src/factories.ts b/src/factories.ts index 657ae1b9..e6a70416 100644 --- a/src/factories.ts +++ b/src/factories.ts @@ -103,6 +103,30 @@ export function tableFromJSON>(array: T[]): Ta return new Table(batch); } +/** @ignore */ +// Like compareTypes, but ignores Dictionary IDs — which are auto-incremented on +// every construction, so two separately-inferred Dictionary types for the same +// data will always have different IDs even though they are structurally identical. +// Recurses through List children and Struct field types for the same reason. +function compareTypesForInference(a: dtypes.DataType, b: dtypes.DataType): boolean { + if (dtypes.DataType.isDictionary(a) && dtypes.DataType.isDictionary(b)) { + return a.isOrdered === b.isOrdered + && compareTypes(a.indices, b.indices) + && compareTypes(a.dictionary, b.dictionary); + } + if (dtypes.DataType.isList(a) && dtypes.DataType.isList(b)) { + return compareTypesForInference(a.children[0].type, b.children[0].type); + } + if (dtypes.DataType.isStruct(a) && dtypes.DataType.isStruct(b)) { + return a.children.length === b.children.length + && a.children.every((f, i) => + f.name === b.children[i].name + && compareTypesForInference(f.type, b.children[i].type) + ); + } + return compareTypes(a, b); +} + /** @ignore */ function inferType(values: T): JavaScriptArrayDataType; function inferType(value: readonly unknown[]): dtypes.DataType { @@ -149,7 +173,7 @@ function inferType(value: readonly unknown[]): dtypes.DataType { } else if (arraysCount + nullsCount === value.length) { const array = value as Array[]; const childType = inferType(array[array.findIndex((ary) => ary != null)]); - if (array.every((ary) => ary == null || compareTypes(childType, inferType(ary)))) { + if (array.every((ary) => ary == null || compareTypesForInference(childType, inferType(ary)))) { return new dtypes.List(new Field('', childType, true)); } } else if (objectsCount + nullsCount === value.length) { diff --git a/test/unit/table/table-test.ts b/test/unit/table/table-test.ts index 01af4090..bc9072a6 100644 --- a/test/unit/table/table-test.ts +++ b/test/unit/table/table-test.ts @@ -56,6 +56,22 @@ describe('tableFromArrays()', () => { expect(table.getChild('d')!.type).toBeInstanceOf(Dictionary); expect(table.getChild('e' as any)).toBeNull(); }); + + test(`creates table from arrays of strings`, () => { + const table = tableFromArrays({ word: [['a', 'b'], ['c', 'd']] }); + expect(table.numRows).toBe(2); + const word = table.getChild('word')!; + expect(word.get(0)!.toArray()).toEqual(['a', 'b']); + expect(word.get(1)!.toArray()).toEqual(['c', 'd']); + }); + + test(`creates table from arrays of objects containing strings`, () => { + const table = tableFromArrays({ customers: [{ names: ['joe'] }, { names: ['bob'] }] }); + expect(table.numRows).toBe(2); + const customers = table.getChild('customers')!; + expect(customers.get(0)!.names.toArray()).toEqual(['joe']); + expect(customers.get(1)!.names.toArray()).toEqual(['bob']); + }); }); @@ -78,4 +94,20 @@ describe('tableFromJSON()', () => { expect(table.getChild('b')!.type).toBeInstanceOf(Bool); expect(table.getChild('c')!.type).toBeInstanceOf(Dictionary); }); + + test(`handles arrays of strings`, () => { + const t1 = tableFromJSON([{ a: ['hi'] }]); + expect(t1.getChild('a')!.get(0)!.toArray()).toEqual(['hi']); + + const t2 = tableFromJSON([{ a: ['hi', 'there'] }]); + expect(t2.getChild('a')!.get(0)!.toArray()).toEqual(['hi', 'there']); + }); + + test(`handles nested objects containing strings`, () => { + const table = tableFromJSON([{ a: [{ b: 'hi' }, { b: 'there' }] }]); + expect(table.numRows).toBe(1); + const rows = table.getChild('a')!.get(0)!; + expect(rows.get(0)!.b).toBe('hi'); + expect(rows.get(1)!.b).toBe('there'); + }); });