I receive regular data from a specific source
. This data consists of nested objects containing numerical values. For example:
{
a: 1,
b: {
c: 2,
d: 3.1,
},
}
My objective is to organize this data into multiple TimeSeries objects of the same structure:
const timeSeries = {
a: new TimeSeries(),
b: {
c: new TimeSeries(),
d: new TimeSeries(),
},
}
Upon each 'update'
event, I need to invoke TimeSeries.append(time, data)
for every new data point:
timeSeries.a.append(time, data.a);
timeSeries.b.c.append(time, data.b.c);
timeSeries.b.d.append(time, data.b.d);
The ultimate goal is to automate this process as the structure of the incoming data evolves.
Currently, I have implemented the JavaScript functionality but struggle with ensuring proper typing in TypeScript. Any suggestions?
// The structure of the data is known during compilation.
// A manual instance initialization is performed elsewhere, which I aim to avoid duplicating for TimeSeries objects.
type Data = {
a: number;
b: {
c: number;
d: number;
}
}
// Definition of the TimeSeries interface used for replication.
interface TimeSeries {
append(time: number, value: number): void;
}
// Utility function
function isNestedValues<V, T extends {}>(value: V | T): value is T {
return typeof value === 'object';
}
// Successful type mapping
type MappedTimeSeries<T extends {}> = {
[P in keyof T]: T[P] extends {} ? MappedTimeSeries<T[P]> : TimeSeries;
};
// Mapping of time series.
// Initially partial until filled with data.
// Preventing premature usage of TimeSeries objects before they are ready.
export const timeSeries = {} as Partial<MappedTimeSeries<Data>>;
// Enhancing strict typing for numeric values would be beneficial
function updateTimeSeriesRecursive<T extends {}>(time: number, o: T, ts: MappedTimeSeries<T>) {
for (const x in o) {
type P = Extract<keyof T, string>;
if (isNestedValues(o[x])) {
// Initialization of TimeSeries{} upon receiving the first set of data
if (!ts[x]) ts[x] = {} as MappedTimeSeries<typeof o[x]>;
updateTimeSeriesRecursive(time, o[x], ts[x]);
} else {
// Initializing actual TimeSeries object upon receiving the first set of data
if (!ts[x]) ts[x] = new TimeSeries();
(ts[x] as TimeSeries).append(time, o[x]);
}
}
}
source.on('update', time: number, (data: Data) => {
updateTimeSeriesRecursive(time, state, timeSeries);
});