In my TypeScript project, I have defined these DTO interfaces:
interface ProductDto {
readonly productId: number;
readonly name : string;
}
interface FirstPartyProductDto extends ProductDto {
readonly foo: string;
readonly bar: number;
}
The application I am working on uses server-side rendering but acts like a single-page application without relying on frameworks like Angular or Vue. To assist with the rehydration process when the page loads in the browser, additional data is included in data-
attributes.
For example, if a page has a list of products, it might be rendered as follows:
<ul class="productsList">
<li
data-product-id="1"
data-product-name="Exploding toilet seat"
>
</li>
<li
data-product-id="2"
data-product-name="Brussels sprout dispenser"
>
</li>
<li
data-product-id="3"
data-product-name="Battery-tester tester"
>
</li>
</ul>
The TypeScript code to rehydrate the ProductDto
is pretty straightforward:
static loadProductFromHtmlElement( e: HTMLElement ): ProductDto {
return loadProductFromDataSet( e.dataset );
}
static loadProductFromDataSet( d: DOMStringMap ): ProductDto {
return {
productId: parseInt( d['productId']!, 10 ),
name : d['productName']!
};
}
If I want to rehydrate instances of FirstPartyProductDto
, the current approach involves manually copying over the members from ProductDto
:
static loadFirstPartyProductFromDataSet( d: DOMStringMap ): FirstPartyProductDto {
const productDto = loadProductFromDataSet( d );
return {
// ProductDto members:
productId: productDto.productId,
name : productDto.name,
// FirstPartyProductDto members:
foo : d['foo']!,
bar : parseInt( d['bar']!, 10 )
};
}
I find this repetition of members between the DTOs cumbersome and inelegant. In untyped JavaScript, I could simply extend the existing object, but that's not feasible here due to type restrictions and read-only properties.
An alternative solution is to use Object.assign or the object spread operator in TypeScript, which can simplify the code by avoiding manual property assignments:
function loadFirstPartyProductFromDataSet( d: DOMStringMap ): FirstPartyProductDto {
const productDto = loadProductFromDataSet( d );
return {
...productDto,
foo: d['foo']!,
bar: parseInt( d['bar']!, 10 )
};
}
While this improves the code slightly by reducing the need for explicit property assignments, it still involves creating a new object instead of directly modifying the existing one.