Skip to content
16 changes: 15 additions & 1 deletion src/builder/struct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,23 @@ import { Field } from '../schema.js';
import { Builder } from '../builder.js';
import { Struct, TypeMap } from '../type.js';

/** @ignore */
type StructValue<T extends TypeMap = any> = Struct<T>['TValue'] | { [P in keyof T]: T[P]['TValue'] };
Copy link
Contributor

@trxcllnt trxcllnt Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed because of a Typescript version bump? This used to work because the mapping is part of the Struct<T>['TValue'] aka StructRowProxy<T>.


/** @ignore */
export class StructBuilder<T extends TypeMap = any, TNull = any> extends Builder<Struct<T>, TNull> {
public setValue(index: number, value: Struct<T>['TValue']) {
public append(value: StructValue<T> | TNull) {
return this.set(this.length, value as any);
}

public set(index: number, value: StructValue<T> | TNull) {
if (this.setValid(index, this.isValid(value as any))) {
this.setValue(index, value as StructValue<T>);
}
return this;
}

public setValue(index: number, value: StructValue<T>) {
const { children, type } = this;
switch (Array.isArray(value) || value.constructor) {
case true: return type.children.forEach((_, i) => children[i].set(index, value[i]));
Expand Down
1 change: 1 addition & 0 deletions test/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"compilerOptions": {
"target": "ESNext",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"rootDir": "../",
"allowJs": true,
"declaration": false,
Expand Down
46 changes: 46 additions & 0 deletions test/unit/builders/struct-builder-plain-object-tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

import { makeBuilder, Field, Struct, StructBuilder, TimestampMillisecond, Float64 } from '../../../src/Arrow.js';

interface ValueType {
time: TimestampMillisecond;
value: Float64;
}

type Value = {
time: number;
value: number;
};

test('StructBuilder accepts plain object and produces correct values', () => {
const children = [
new Field('time', new TimestampMillisecond()),
new Field('value', new Float64()),
];
const structType = new Struct<any>(children as any);
const builder = makeBuilder({ type: structType, nullValues: [null, undefined] }) as StructBuilder<any, null | undefined>;

const value: Value = { time: 1_630_000_000_000, value: 42.5 };
builder.append(value);
const vec = builder.finish().toVector();
const row = vec.get(0)! as any;

expect(row.time).toBe(value.time);
expect(row.value).toBeCloseTo(value.value);
expect(row.toJSON()).toEqual({ time: value.time, value: value.value });
});