Reading from JSON file, variable name is different, does not match - javascript

I am trying to read from a JSON file, but it is formatted this way:
[
{
"INV#" : "123"
},
{
"INV#" : "456"
}
]
That variable name does not meet JavaScript or TypeScript naming. So I have the following object:
export interface Invoice {
invoiceNumber: number;
}
class myclass {
private inv: Invoice[];
}
How can I read from that json file into this object. Making changes on the database variable is not an option. Changing the name of my interface variable is not possible in JavaScript. Somehow they have to match, but there must be a way to make the object property into mine.

// let's assume that you've read the json into some variable
let response = [
{
"INV#" : "123"
},
{
"INV#" : "456"
}
];
export interface Invoice {
invoiceNumber: number;
}
class myclass {
private inv: Invoice[];
constructor(_response: any[]) {
// do not forget to check _response for null/undefined, if it is an array or not, etc.
this.inv = _response.map(obj => <Invoice>{
invoiceNumber: parseInt(obj['INV#'])
});
}
}
// usage:
let some_var = new myclass(response);
// now some_var contains array of invoices internally.

Related

Define reusable code blocks in JavaScript / TypeScript?

My project has lots of serializable classes where I need to convert some attributes before using the class. For example, this is the Json result of a Rest-Call:
{
"class": "ProductGroup",
"name": "RootGroup",
"children": [
{
"class": "ProductGroup",
"name": "RootGroup",
"children": [
// .... and so on... you get the idea.
]
}
]
}
I've created a serializable.ts class which basically makes sure, that I habe an object of type ProductGroup instead of Object:
class ProductGroup {
class:string
name:string
constructor(json?: any) {
Object.assign(this, json)
}
}
var myObject = new ProductGroup(jsonResult)
but to make sure that the children are also of type ProductGroup, I have to convert them as well. This is, how I currently do it:
class ProductGroup {
class:string
name:string
constructor(json?: any) {
Object.assign(this, json)
}
private _children:ProductGroup[]
get children():ProductGroup[] { return this._children }
set children(_:any[]) { this._children = _.map(x => new ProductGroup(x) }
}
var myObject = new ProductGroup(jsonResult)
My problem is: I need those three lines of code for every kind of child object of the Json-Response, to make sure, it's converted to the correct class. So I was wondering if there might be some kind of define that helps me re-use this three-line-code-block.... something like this (pseudocode)
const typesafe = <T>(name:string, ctor:Type<T>) => {
private ${'_'+name}:T[]
get ${name}():T[] { return this.${'_'+name} }
set ${name}(_:any[]) { this.${'_'+name} = _.map(x => new ctor(x) }
}
class ProductGroup {
class:string
name:string
constructor(json?: any) {
Object.assign(this, json)
}
typesafe('children', ProductGroup)
typesafe('products', Product)
// ... I think you get the point :)
}
var myObject = new ProductGroup(jsonResult)
is there any way to achieve this?
see code above
google / stackoverflow search for existing answers

How to use javascript object destructuring on a subset of an argument and maintain reference to all remaining properties in single value?

I'm trying to figure out if there's a way to use typescript/javascript object destructuring to target only a subset of the objects properties while maintaining the remaining properties of the object in its own variable, "catching all other properties/keys".
My use case is related to using inheritance to extending a class while attempting to leave the api's the similar. In the example below I only want to use object destruction for properties onlyForB and alsoOnlyForB while pass all remaining properties to config variable.
class A {
constructor(config) {
this.name = config.name;
// ...
}
}
class B extends A {
constructor({ onlyForB, alsoOnlyForB, ..config }) { // using ...config doesn't work
this.onlyForB = onlyForB;
this.alsoOnlyForB = alsoOnlyForB;
super(config);
}
}
const b = new B({ onlyForB: "B", alsoOnlyForB: "B2", name: "Satoshi", age: 100});
/**
Trying to achieve the following in class B's constructor
onlyForB -> "B"
alsoOnlyForB -> "B2"
config -> { name: "Satoshi", age: 100 }
*/
When I try this with { onlyForB, alsoOnlyForB, ..config } which is similar to creating objects with the spread syntax I get errors. In the real use case I am extending an open source class that has mainly "config" properties and using typescript.
Is there a way to achieve this without manually deleting all the object's properties specific to the B class?
Side Note: For those familiar with python I am trying to achieve something similar to what **kwargs does, ie. def __init__(self, onlyForB, alsoOnlyForB, **kwargs).
There are lots of possible ways to do something like this; here is one example:
type ConfigForB = {
onlyForB: string,
alsoOnlyForB: string,
age: number,
name: string
}
type ConfigForA = Omit<ConfigForB, 'onlyForB' | 'alsoOnlyForB'>
class A {
private name: string
constructor(config: ConfigForA) {
this.name = config.name;
// ...
}
}
class B extends A {
private onlyForB: string
private alsoOnlyForB: string
constructor({ onlyForB, alsoOnlyForB, ...config }: ConfigForB) {
super(config);
this.onlyForB = onlyForB;
this.alsoOnlyForB = alsoOnlyForB;
}
}
const b = new B({ onlyForB: "B", alsoOnlyForB: "B2", name: "Satoshi", age: 100});
I've defined a ConfigForB type that has the four properties listed in your example, and used that type in the constructor for B. Then I've used the Omit<Type, Keys> utility type to construct a new ConfigForA type, which is exactly the same as ConfigForB, except it's without the onlyForB and alsoOnlyForB keys.
If I were to define this explicitly, it would look like this:
type ConfigForA = {
age: number,
name: string
}
This would also work, by the way - using Omit is just nice since you don't have to repeat yourself.
You can try this out on TS Playground here.

How do I work with TypeScript correctly when I need to change a type of a property in an object

I have this class that represents JSON incoming from POST request.
class MilkCarton {
company: string;
price: number;
expiredAt?: string;
// ...20 more properties
}
Before I store it in my mongoDB database, I want to change the type of expiredAt to Date, so I have another class that represents the schema of the database.
class MilkCartonSchema: {
company: string;
price: number;
expiredAt?: Date;
// ...20 more properties
}
expiredAt can be null, I want to create an object that copies the object of type MilkCarton but has its expiredAt converted to Date
prepareMilkForDb(milk: MilkCarton): MilkCartonSchema {
const preparedMilk = {
...milk,
}
if (milk.expiredAt) {
preparedMilk.expiredAt = new Date(milk.expiredAt)
}
return preparedMilk;
}
But I run into an error because preparedMilk has already inferred its type and has expiredAt as string, it can't be turned into Date from TypeScript perspective if it's string. But I want it to turn into Date, what is the approach to do that correctly in TypeScript?
EDIT:
I ended up going with:
prepareMilkForDb(milk: MilkCarton): MilkCartonSchema {
const preparedMilk = {
...milk,
...(milk.expiredAt && {
expiredAt: new Date(milk.expiredAt)
}),
}
return preparedMilk;
}
This works, but isn't exactly what I wished for, if I had another example with 3 different deep nested ISO string properties that need converting to Date (and backwards) it would be very dirty to do this workaround.
Does this work for you?
interface MilkCarton {
company: string;
price: number;
expiredAt?: string;
// ...20 more properties
}
interface MilkCartonSchema {
company: string;
price: number;
expiredAt?: Date;
// ...20 more properties
}
const prepareMilkForDb = (milk: MilkCarton): MilkCartonSchema => {
return {
...milk,
expiredAt: milk.expiredAt == null ? undefined : new Date(milk.expiredAt)
};
}
Typescript sandbox
Note: I changed the classes to interface, I don't know if that's ok or not?

Parse wrapped typed array with Axios

I am using TypeScript and Axios. I have the following classes:
class Item {
private a: string = '';
private b: string = '';
public constructor(options: any = {}) {
Object.assign(this, options);
}
}
class Items {
private items: Array<Item> = [];
public constructor(options: any = {}) {
Object.assign(this, options);
}
public getItems(): Array<Item> {
return this.items;
}
}
My backend responses are similar to:
{
"items": [
{"a": "a1", "b": "b1", "c": "c1"},
{"a": "a2", "b": "b2", "c": "c2"}
]
}
As a result I want to get an Items class that contains all Items with the fields a and b (c should be ignored).
I tried the following:
Axios.get<Items>('path-to-backend-api').then((response) => {
const items: Items = (new Items(response.data));
});
However, the resulting items object's inner array is empty. I also wanted to invoke JS's map on the response.data.items array, but couldn't get the type system to work. const items: response.data.getItems() failed as well. I tried about a dozen other less typed approaches, but couldn't find a solution.
How could I construct a Array<Item object based on the above JSON response?
Okay, multiple things to unpack here.
The generic type passed to Axios.get<T>() should represent the shape of incoming deserialised data i.e. response.data. You are passing Items which is a class you are creating a new instance of using the incoming data. They are separate and should be declared separately.
Whilst you can use a class as an interface, it doesn't really suit your implementation to do so.
response.data.getItems() is impossible with your current implementation. If you really want to be able to do this (you probably don't), you could add a transformResponse function to your Axios request config that creates a new class instance from the data before delivering it in the response of Axios.get().
With minimal changes to your original code, here's what I'd suggest doing to type correctly and make your code more flexible:
interface ItemData {
a: string;
b: string;
}
interface ItemsData {
items: ItemData[];
}
class Item implements ItemData {
a: string = '';
b: string = '';
constructor(options: ItemData) {
Object.assign(this, options);
}
}
class Items {
items: Item[] = [];
constructor(options: ItemsData) {
Object.assign(this.items, options.items.map(item => new Item(item));
}
getItems(): Item[] {
return this.items;
}
}
Axios.get<ItemsData>('path-to-backend-api').then((response) => {
const items: Items = new Items(response.data);
// now you can use the class methods
const list = items.getItems();
});

How to use dynamic object properties in TypeScript

I'm working with TypeScript and I need to deal with dynamic object properties. I wish that I could be a master in TypeScript but I am not. The problem I have is that in my current project, I would like to give users the possibility to input their own custom interface to a Generic interface.
What I got working now is the following interface
export interface Filter {
columnId: string;
searchTerm: string;
}
export interface GridState {
filters: Filter[];
}
// use it as an array of filters
const state: GridState = {
filters: [{ columnId: 'firtName', searchTerm: 'John' }]
}
However from what is shown above, I would like to give the user the possibility to use his own interface. So let say that instead of columnId, he wants to use field and instead of searchTerm he would use value. So his custom interface would be
export interface CustomFilter {
field: string;
value: string;
}
What I would like to provide to the user is something like this, a custom structure template
export interface FilterStruct {
propNameColumnId: string;
propNameSearchTerm: string;
}
but how do I connect the 2 together? How do I use the user's CustomFilter with the FilterStruct. The following doesn't work and I know it's not correct.
export interface GridState {
filters: FilterStruct<CustomFilter>[];
}
The end goal is that the user would be able to input with his own interface and his own property names. Then on my side, I would just loop through the array with the dynamic object property that was provided.
You can make GridState generic and provide a default value for the generic parameter. Also FilterStruct can inherit Array, so we can add extra properties to the array, using a helper function :
export interface FilterStruct<T> extends Array<T> {
// We make sure the property names are actually properties of T
// We make these optional you should use default values if they are undefined
// We do this to keep initialization simple in the non customized scenario, you can make them mandatory, but then you can't initialize with a simple array
propNameColumnId?: keyof T;
propNameSearchTerm?: keyof T;
}
export interface Filter {
columnId: string;
searchTerm: string;
}
export interface CustomFilter {
field: string;
value: string;
}
// T has a default value of Filter so we don't have to specify it, unless we want to customize
export interface GridState<T = Filter> {
filters: FilterStruct<T>;
}
// Helper function to create an array with the extra properties
function createFilterStruct<T>(cfg: { propNameColumnId: keyof T; propNameSearchTerm: keyof T; }, items: T[]) {
return Object.assign(items, cfg);
}
// Default we can use simple array initailization
const state: GridState = {
filters: [{ columnId: 'firtName', searchTerm: 'John' }]
}
// Custom filter, create with createFilterStruct
const stateCustom: GridState<CustomFilter> = {
filters: createFilterStruct({ propNameColumnId: 'value', propNameSearchTerm: 'field' }, [
{ value: 'firtName', field: 'John' }
])
}
//Usage
function loopThrough<T>(grid: GridState<T>){
// Provide defaults for propNameColumnId and propNameSearchTerm
let propNameColumnId = grid.filters.propNameColumnId || 'columnId' as keyof T
let propNameSearchTerm = grid.filters.propNameSearchTerm || 'searchTerm' as keyof T
// Loop throught the array normally, it is just an array
for(let filter of grid.filters){
// Access the properties
console.log(`${filter[propNameColumnId]} = ${filter[propNameSearchTerm]}`);
}
}
loopThrough(stateCustom);
loopThrough(state);

Categories

Resources