Map JSON to existing deep object structure - javascript

Say I have the following Typescript model:
class Person{
public Address: Address;
public FirstName: string;
public LastName: string;
constructor(){
this.Address = new Address();
}
}
And I get an exact representation of this object from a server via JSON.
How would I go about generically setting the properties of both the Person and the Address but leave the existing objects intact.
So similar to this, but generically:
public SetData(json:any){
this.Address.City = json.Address.City;
this.Address.Province = json.Address.Province;
this.FirstName = json.FirstName;
}
The gotcha being that the original objects must remain and have there setters called as they are Mobx observables. This rules out Object.assign and any 'extend' methods I have found.
Thanks.

In somewhat simplified case you can do it manually without too much effort:
class Address
{
public City: string;
public Province: string;
}
class Person{
public Address: Address;
public FirstName: string;
public LastName: string;
constructor() {
this.Address = new Address();
}
private SetDataInternal(target: any, json: any)
{
if (typeof json === "undefined" || json === null)
{
return;
}
for (let propName of Object.keys(json))
{
const val = target[propName];
if (typeof val === "object")
{
this.SetDataInternal(val, json[propName]);
}
else
{
target[propName] = json[propName];
}
}
}
public SetData(json: any)
{
this.SetDataInternal(this, json);
}
}
const json = {
Address: {
City: "AAA",
Province: "BBB"
},
FirstName: "CCC"
}
const p = new Person();
p.SetData(json);
console.log(p);
It surely miss some checks and corner cases validations, but apart from that it does what you ask for.

My final implementation based of Amids:
import * as _ from "underscore";
export class ObjectMapper
{
public static MapObject(source: any, destination: any) {
_.mapObject(source, (val, key) => {
if(_.isObject(val))
{
this.MapObject(val, destination[key]);
}
else if(_.isArray(val))
{
const array = destination[key];
for(var i in val)
{
const newObject = {};
_.extend(newObject, val[i]);
array.push(newObject);
}
}
else
{
destination[key] = val;
}
});
}
}

Related

how to inheritance typescript class from javascript class

I use the Javascript module to extending Class for Custom extended Class.
I wrote my Custom Class in typescript, And I met an error below messages.
Property 'jsFunc' does not exist on type 'tsClass'.ts(2339)
I think Because Javascript Class have no type information, So It can't bring any properties.
how to correctly work on this problem.
example
book.js
class book {
page;
constructor(page) {
this.page = page;
}
open() {
_next();
}
_next() {
this.page = this.page++;
}
}
comicbook.ts
class commicbook extends book {
page; // if it isn't It would be error that does not exist
open() {
this.page = 10;
_next(); // Property '_next()' does not exist on type 'commicbook'.ts
}
}
To call a method on your current class, you need to prepend this:
this._next();
One way to inherit typescript class from javascript class would be :
class Person {
firstName: string;
lastName: string;
constructor (fName: string, lName: string) {
this.firstName = fName;
this.lastName = lName;
}
getFullName() {
return `${firstName} ${lastName}`;
}
}
class Employee extends Person {
empID: string;
designation: string;
constructor (fName: string, lName: string,
eID: string, desig: string) {
super(fName, lName);
this.empID = eID;
this.designation = desig;
}
toString() {
return `${empID} - ${firstName} ${lastName}
=> ${designation}`;
}
}

how to call a function class javascript

I wanted to create a class with private parameters and functions to access the data I want. You can see this :
export class Product {
private name: string;
private type: string;
private longDetail: string;
private shortDetail: string;
private stock: number;
private price: number;
private linkImage: string;
private id: number;
constructor(
name: string,
type: string,
longDetail: string,
shortDetail: string,
stock: number,
price: number,
linkImage: string,
id: number
) {
this.name = name;
this.type = type;
this.longDetail = longDetail;
this.shortDetail = shortDetail;
this.stock = stock;
this.price = price;
this.linkImage = linkImage;
this.id = id;
}
getName(): string {
return this.name;
}
getType(): string {
return this.type;
}
getLongDetail(): string {
return this.longDetail;
}
getShortDetail(): string {
return this.shortDetail;
}
getStock(): number {
return this.stock;
}
getPrice(): number {
return this.price;
}
getLinkImage(): string {
return this.linkImage;
}
getId(): number {
return this.id;
}
}
And when I want to call a function in a component I am told :
ProductListComponent.html:15 ERROR TypeError: newProduct.getName is not a function
Do you have a solution ? Thank you very much in advance !
EDIT :
This is the code called after the click in front end
addProductBasket(newProduct: Product) {
const newClientBasket = this.createNewClientBasketWithAdd(
this.clientBasket.getValue(),
newProduct
)
this.clientBasket.next(newClientBasket)
console.log(newClientBasket)
}
private createNewClientBasketWithAdd(
oldClientBasket: BasketProduct[],
newProduct: Product
): BasketProduct[] {
const found = oldClientBasket.find((product) => {
if (product.getId() === newProduct.getId()) {
product.addOneProduct()
}
})
if (found === undefined) {
console.log(newProduct.getName())
oldClientBasket.push(
new BasketProduct(
newProduct.getName(),
newProduct.getType(),
newProduct.getLongDetail(),
newProduct.getShortDetail(),
newProduct.getStock(),
newProduct.getPrice(),
newProduct.getLinkImage(),
newProduct.getId()
)
)
}
return oldClientBasket
}
It's my apiservice to get data
export class ApiService {
private dataApi: BehaviorSubject<Product[]> = new BehaviorSubject<Product[]>([]);
constructor(private http: HttpClient) {
this.getDataFromApi();
}
private getDataFromApi(){
this.http
.get<Product[]>("../../assets/data.json")
.toPromise()
.then((data) => this.dataApi.next(data));
}
public getData():Observable<Product[]>{
return this.dataApi.asObservable();
}
}
You should have an instance of Product class before accessing its methods.
var newProduct = new Product();
newProduct.getName();
After David's help in commenting, I understood that I had to instantiate the data I receive in http client.
I then modified the constructor and my client http get
constructor(obj: any) {
Object.assign(this, obj);
}
and
private getDataFromApi(){
this.http
.get<Product[]>("../../assets/data.json").pipe()
.toPromise()
.then((data) => {
const productList = data.map(product => new Product(product));
this.dataApi.next(productList)});
}

Working with maps in Typescript

I am having a bit of trouble working with maps int Typescript. What I am trying to do is to use a HashMap smilier to that found in Java for example here is my Java object,
public class Payment {
private String id;
private DateTime created;
//when they actually received the payment
private DateTime paidDate;
private String customerId;
private String companyId;
private BigDecimal amount;
private BigDecimal allocated;
private String description;
// it must be the Id of PaymentMethod
private String type;
//to improve it
private String currency;
private Boolean fullPaid = false;
private Boolean lockArchive = false;
//<InvoiceId, Amount>
private HashMap<String, BigDecimal> invoices = new HashMap<>();
//getter and setters....
So what I have done in Typescript it to create a similar class for example,
export class Payment {
public id?:string;
public created?:string;
public paid_date?:Date;
public customer_id?:string;
public company_id?:string;
public amount?:number;
public allocated?:number;
public description?:string;
public type?:string;
public currency?:string;
public full_paid?:boolean;
public lock_archive?:boolean;
public invoices: {[invoice_id:string]:number};
constructor(id: string, created: string, paid_date: Date, customer_id: string, company_id: string, amount: number, allocated: number, description: string, type: string, currency: string, full_paid: boolean, lock_archive: boolean, invoices: { [p: string]: number }) {
this.id = id;
this.created = created;
this.paid_date = paid_date;
this.customer_id = customer_id;
this.company_id = company_id;
this.amount = amount;
this.allocated = allocated;
this.description = description;
this.type = type;
this.currency = currency;
this.full_paid = full_paid;
this.lock_archive = lock_archive;
this.invoices = invoices;
}
}
I am trying to to basically add to the the invoices map so I have the following function,
public invoicesMap = new Map<string, number>();
constructor(public dialogRef: MdDialogRef<PaymentInvoiceSelectDialogComponent>,
private customerService:CustomerService,
private paymentServices: PaymentsService) {
}
ngOnInit() {
this.customer.subscribe((res)=>{
this.customer_name = res.customer_name
}, error=>{
console.error(<any>error);
});
this.customerService.getListOfCustomerInvoices(this.customerId,'0','25')
.subscribe( (res) =>{
this.invoices = res;
},
error => {
console.error(<any>error);
});
}
selectedInvoice(invoiceId: string, amount: number, event:any) {
if(event.checked){
if(!_.isEmpty(this.payment.invoices)){
for(let [key, value] of this.invoicesMap) {
if (key !== invoiceId) {
this.invoicesMap.set(invoiceId, amount);
for(let [key, vvalue] of this.invoicesMap) {
if (key === invoiceId) {
this.availableAllocation = this.availableAllocation - vvalue;
}
}
}
}
} else {
this.invoicesMap.set(invoiceId,amount);
for(let [key, vvalue] of this.invoicesMap) {
if(key === invoiceId){
this.availableAllocation = this.amount - vvalue;
}
}
}
} else if(!event.checked){
for(let [key, vvalue] of this.invoicesMap) {
if (key === invoiceId) {
console.log("removing an item");
this.availableAllocation += vvalue;
}
}
}
//at this point I would like to set this.payment.invoices to this.invoiceMap
for(let [key, value] of this.invoicesMap){
let v = {invoice_id:key,amount:value};
console.log(v);
}
this.payment.allocated = this.availableAllocation;
}
savePayment(){
console.log(JSON.stringify(this.payment));
// this.paymentServices.updatePayment(this.payment)
// .subscribe((res)=>{
// this.payment = res;
// this.dialogRef.close(this.payment);
// },
// error =>{
// console.log(<any>error);
// });
}
the items are added to the invoiceMap but the problem I am having now is adding invoiceMap to payment.invoices. If someone can point me in the right direction that would be much appreciated. Thanks
You can change it to a Map in Payment as well:
public invoices: Map<string, number>;
And then you can simply assign the map that you have.
Or you can iterate over the map entries and turn them into an object like you have in Payment:
let m = new Map<string, number>();
let m2 = {} as { [key: string]: number };
m.forEach((value, key) => m2[key] = value);
If you are gonna use String as keys there is no reason to use a hashmap, every object in javascript, as well as typescript, is a simple map. The only reason you would need to use the Map class is if you need something other than a String as a key.
Please take a look at this question -> How is a JavaScript hash map implemented?
if I understand what do you want you can use TypeLite : http://type.litesolutions.net/
this addon convert any class c# in typescript class.
before the async comunication you parse the request to json and into your code behind( or into controller )you response with your object serialized into json.
The most simple way is to use Record type Record<string, any>
const invoicesMap : Record<string, any>
This would allow you to have a type with any any string and values. Only use it if you don't know the keys in advance.
You can also look at Partial type. If you have some values but not all.
https://www.typescriptlang.org/docs/handbook/utility-types.html

Check if Object contains string in TypeScript

In my TypeScript I have this class:
export class PhoneBookPerson {
Email: string;
Name: string;
Phonenumber: string;
ProfileImg: string;
Department: string;
JobTitle: string;
Username: string;
}
I'm wondering how can I check if any of the properties contains a specific value.
let $SearchTerm = target.val();
function RetrievedUsers(sender: any, args: any) {
for (let i = 0; i < users.get_count(); i++) {
let user = users.getItemAtIndex(i);
let person = new PhoneBookPerson();
person.Name = user.get_loginName();
person.Email = user.get_email();
person.Username = user.get_loginName();
person.JobTitle = user.get_title();
<-- search of person contains value from $SearchTerm
usermatch.push(person);
}
}
Iterate over object properties and check if any of them contains specified text.
Sample function to do so (I assume only string properties in object)
function objectContains(obj, term: string): boolean {
for (let key in obj){
if (obj[key].indexOf(term) != -1) return true;
}
return false;
}
Example usage
if (objectContains(person, $SearchTerm)) {
// do something
}

Storing interfaces in object

Imagine I have the following interfaces
interface IMarket {
ID: number,
Name: string,
MarketDescription: string
}
interface IDepartment {
ID: number,
Name: string,
DepartmentDescription: string
}
Is there a way to store the interfaces in an object like this?
var typeMap = { Markets: IMarket, Departments: IDepartment }
I'd like to do something like this. I'd like to dynamically set the generic type for "getQueryResults" based on a string value I pass into the constructor.
export class Service {
protected baseURL = "";
protected typeName = "";
private typeMap = { Markets: IMarket, Departments: IDepartment }
constructor(typeName) {
this.baseURL = 'http://localhost/API/odata/' + typeName;
this.currentType = typeMap[typeName];
}
getQueryResults(): Promise<this.currentType> {
return new Promise<this.currentType>((resolve, reject) => {
$.getJSON(this.baseURL, function (returnValue) {
resolve(returnValue.value);
});
})
}
}
var marketService = new Service("Markets");
var topMarket = marketService.getQueryResults();
//topMarket is an instance(?) of IMarket
var departmentService = new Service("Departments");
var topDepartment = departmentServicegetQueryResults();
//topDepartment is an instance(?) of IDepartment
That can be simply solved using generics, it's exactly what it's for:
export class Service<T> {
protected baseURL = "";
constructor() {
this.baseURL = 'http://localhost/API/odata/' + typeName;
}
getQueryResults(): Promise<T> {
return new Promise<T>((resolve, reject) => {
$.getJSON(this.baseURL, function (returnValue) {
resolve(returnValue.value);
});
})
}
}
var marketService = new Service<IMarket>();
var topMarket: Promise<IMarket> = marketService.getQueryResults();
var departmentService = new Service<IDepartment>();
var topDepartment: Promise<IDepartment> = departmentService.getQueryResults();
Edit
You can use 2 more classes to "get rid" of the need to have Service<TYPE> more than once (per TYPE):
export abstract class Service<T> {
protected baseURL = "";
constructor() {
this.baseURL = 'http://localhost/API/odata/' + this.getTypeName();
}
getQueryResults(): Promise<T> {
return new Promise<T>((resolve, reject) => {
$.getJSON(this.baseURL, function (returnValue) {
resolve(returnValue.value);
});
})
}
protected abstract getTypeName(): string;
}
export class MarketsService extends Service<IMarket> {
protected getTypeName(): string {
return "Markets";
}
}
export class DepartmentsService extends Service<IDepartment> {
protected getTypeName(): string {
return "Departments";
}
}
var marketService = new MarketsService();
var topMarket: Promise<IMarket> = marketService.getQueryResults();
var departmentService = new DepartmentsService();
var topDepartment: Promise<IDepartment> = departmentService.getQueryResults();
But unlike the need to specify the type every time you use Service, these extra classes will be part of the compiled js, so it's a question of what's more important to you.
Taking a note from the TypeScript docs:
http://www.typescriptlang.org/docs/handbook/namespaces.html#namespaced-validators
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
It appears you would want:
namespace YourNamespace {
export interface IMarket {
ID: number,
Name: string,
MarketDescription: string
}
export interface IDepartment {
ID: number,
Name: string,
DepartmentDescription: string
}
}

Categories