The AlphaVantage API has spaces and periods in the keys. There is no formal doco for the API, though you can see it in their demo url
In my Typescript app I created data structs for this (I'm happy for anyone to copy and use these - perhaps after the solution to my question is found):
export class MetaData {
'1. Information': string
'2. Symbol': string
'3. Last Refreshed': string
'4. Output Size': string
'5. Time Zone': string
constructor(one, two, three, four, five) {
this['1. Information'] = one
this['2. Symbol'] = two
this['3. Last Refreshed'] = three
this['4. Output Size'] = four
this['5. Time Zone'] = five
}
}
export interface TimeSeries {
[date: string]: {
'1. open': string;
'2. high': string;
'3. low': string;
'4. close': string;
'5. volume': string;
}
}
export interface AlphaVantage {
'Meta Data': MetaData;
'Time Series (Daily)'?: TimeSeries;
'Time Series (Weekly)'?: TimeSeries;
}
I call the API using alphavantage
from NPM and implicitly cast it to my AlphaVantage
:
const av: AlphaVantage = await alpha.data.weekly(options.stocks, 'compact', 'json')
And then (potentially after some massaging etc) I persist it in a MongoDB collection:
const doc = await this.model.findByIdAndUpdate(proxyModel._id, proxyModel)
(The ProxyModel is a DTO used to define database keys such as date, stock symbol etc... One of the fields is the AlphaVantage data).
This must serialize the data and it errors with:
key 1. Information must not contain '.'
Is there an easy way to handle this. My choice would be to create equivalent objects without spaces:
export interface TimeSeries {
[date: string]: {
'1_open': string;
'2_high': string;
'3_low': string;
'4_close': string;
'5_volume': string;
}
}
And then cast to this. In which case provide a mapping ...
I can see my self creating an implementation. However before I get in to this I'd like to hear of any ideas how best to handle this data structure.
I wrote a solution with a simple object key mapping function.
I tried to also use AutoMapper-ts - https://www.npmjs.com/package/automapper-ts - so as to make it more clearer about the changes being made. However it became too difficult to map all cases. I couldn't get the tests (that act as doco) working in the time I allocated to it. I've just seen that there is https://github.com/gonza-lito/AutoMapper, which is a more recently modified fork. However it is not npm install
ing out of the box.
This is the class I came up with to solve my problem:
export class ObjectUtils {
static fixKey(key) {
const upperCaseReplacer = (match: string, g1: string) => {
return g1.toUpperCase()
}
const wordsReplacer = (match: string, g1: string, g2: string) => {
return g1 + g2.toUpperCase()
}
const m1 = key.replace(/^\d+ *\. *([a-zA-Z])/, upperCaseReplacer)
const m2 = m1.replace(/([a-zA-Z]) +([a-zA-Z])/g, wordsReplacer)
const out = m2.replace(/^([a-zA-Z])/, upperCaseReplacer)
return out
}
static fixObj(obj: any) {
let newObj = null
if (Array.isArray(obj)) {
newObj = []
for (const i of obj) {
newObj.push(ObjectUtils.fixObj(i))
}
return newObj
} else if (typeof obj === 'object') {
newObj = {}
for (const key of Object.keys(obj)) {
newObj[ObjectUtils.fixKey(key)] = ObjectUtils.fixObj(obj[key])
}
return newObj
} else {
return obj
}
}
}
This creates the following:
export class Metadata {
Information: string
Symbol: string
LastRefreshed: string
OutputSize: string
TimeZone: string
}
export interface TimeSeries {
[date: string]: {
Open: string;
High: string;
Low: string;
Close: string;
Volume: string;
}
}
export interface AlphaVantage {
Metadata: Metadata
DailyTimeSeries?: TimeSeries
WeeklyTimeSeries?: TimeSeries
}