Working with maps in Typescript - javascript

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

Related

Error trying to create a Dictionary object in Typescript

I need to create a dictionary object in typescript, something that has the following function:
obj.add(key, value);
obj.remove(key);
obj.contains(key); // returns true/false
obj.get(key); // returns value
This was the model I created to accommodate:
export class Dictionary<T, F> {
items = {};
constructor() {
this.items = {};
}
public contains(key: T): boolean {
return key in this.items;
}
public add(key: T, value: F): void {
this.items[key] = value;
}
public get(key: T): F {
return this.items[key];
}
public remove(key: T): boolean {
if (this.contains(key) ){
delete this.items[key];
return true;
}
return false;
}
}
However, I keep getting this error:
Error: src/app/models/dictionary.model.ts:10:7 - error TS2536: Type 'T' cannot be used to index type '{}'.
10 this.items[key] = value;
Has anyone come across this before and can recommend the most appropriate fix?
Thanks in advance for any pointers (examples welcome :-))!

RxJs Websocket : Object's property (object) with no defined type not sent

Using rxjs websocket, I'm facing an issue when sending data.
I always wrap the message I send into a Request object that looks like this :
export class Request {
public constructor() { }
private t: string = null;
public get type(): string {return this.t;}
public set type(type: string) {this.t = type;}
private i: string = null;
public get issuer(): string {return this.i;}
public set issuer(issuer: string) {this.i = issuer;}
private n: string = null;
public get name(): string {return this.n;}
public set name(name: string) {this.n = name;}
private c: MyCandidateClass = null;
public get candidate(): MyCandidateClass {return this.c;}
public set candidate(candidate: MyCandidateClass) {this.c = candidate;}
private cis: { [id: string]: string; } = {};
public get credentialsInfo(): { [id: string]: string; } {return this.cis;}
public set credentialsInfo(credentialsInfo: { [id: string]: string; }) {this.cis = credentialsInfo;}
}
The object is built, and my cis property is a key/value map which value can be for example {_PinCode: "1234"}.
Everything is fine in my request object, but here is what's actually sent to the websocket server :
{
cis: {},
c: candidate // <- an object of class MyCandidateClass as expected
i: "some_username",
n: "some_name",
t: "some_type"
}
I am losing the cis object.
While investigating, I noticed that if I create a class CredentialsInfo with the correct properties inside and change my cis property from Request like the following :
private cis: CredentialsInfo = null;
public get credentialsInfo(): CredentialsInfo {return this.cis;}
public set credentialsInfo(credentialsInfo: CredentialsInfo) {this.cis = credentialsInfo;}
With these modifications, it works just fine.
However, I want my cis to be key/value map as defined in my initial Request, not a defined type of my own since I can't always know what will the map keys be.
I'm guessing this has to deal with default serialization/deserialization ?
How can I achieve this, and make the initial Request work ?
Note : I'm using Angular and rxjs, here are some relevant parts of my websocket service :
import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
// ...
ws: WebSocketSubject<any> = null;
// ...
connect(): void {
// ...
this.ws = webSocket(this.websocketUrl);
this.ws.subscribe(
(response) => this.onReceive(ResponseInfo.map(response)),
(err) => this.onError(err),
() => this.onClose('Complete (connection closed)')
);
// ...
}
send(request: RequestInfo): void {
// ...
this.ws.next(request);
}
// ...

Map JSON to existing deep object structure

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;
}
});
}
}

Typescript: How to deserialize JSON object with private variables?

I have the following typescript model object user
export class User {
constructor(
private _name: string,
private _email: string
) {}
public get name():string {
return this._name;
}
public set name(value:string) {
this._name = value;
}
get email():string {
return this._email;
}
set email(value:string) {
this._email = value;
}
}
I store the object via this code:
let user = new User('myName', 'myEmail');
localStorage.setItem('user', JSON.stringify(user));
If I look into the local storage there is the following string:
{"_name":"myName","_email":"myEmail"}
How do I get the user object again?
let user: User = JSON.parse(localStorage.getItem('user'));
console.log(user.name); // logs undefined. Should log 'myName'
console.log(user._name); // this logs 'myName'. But according to the typescript documentation this should NOT work!
I guess this has something to to with the underscores the object is stored with.
How can I receive the object correctly?
You need to implement some serialize and deserialize methods in your model.
class User {
static public deserialize(serialized) {
const {name, email} = JSON.parse(serialized);
return new User(name, email);
}
constructor(
private _name: string,
private _email: string
) {}
public get name():string {
return this._name;
}
public set name(value:string) {
this._name = value;
}
get email():string {
return this._email;
}
set email(value:string) {
this._email = value;
}
public serialize() {
return JSON.stringify({name: this.name, email: this.email});
}
}
let user = new User('myName', 'myEmail');
localStorage.setItem('user', user.serialize());
let user1: User = User.deserialize(localStorage.getItem('user'));
Thanks to this answer to another post, I came up with this solution based on JSON.parse()'s reviver parameter:
const data = JSON.parse(json, function (key, value) {
if (key.startsWith('_')) {
this[key.slice(1)] = value
return
}
return value
})
This works the other way around too with JSON.stringify()'s replacer parameter.
Note that the callback won't work with an arrow function since they don't have their own binding to this

java library for parsing javaScript objects with numeric keys

I have some problem parsing javaScript objects received from a web server.
These objects supposed to be JSON but contains numeric keys.
javaScript object:
{a: "alpha", 2: "two"}
The question is how can i decode such javascript objects in java ?
Are there any existing libraries that could be used ? And if there are how to represent that object in java :
public class JavaObject {
private String a; // for field - a: "alpha"
private ??? // for field - 2: "two"
}
Thanks
As i didn't find any solution for my problem on the web, i implemented myself an adapter for GSON library to handle such data.
As well known JSON supports only strings keys.
If a have a json data like this {"a": "alpha", "2": "two", "3":3} i can convert it in a regular JSONObject, but still i can't convert it it my own java object.
To handle this situation i created a ObjectMap object
// ObjectMap.java
import java.util.Map;
public class ObjectMap<V> {
private Map<String, V> map;
public ObjectMap(Map<String, V> map) {
setMap(map);
}
public Map<String, V> getMap() {
return map;
}
public void setMap(Map<String, V> map) {
this.map = map;
}
#Override
public String toString() {
return "ObjectMap [map=" + map + "]";
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((map == null) ? 0 : map.hashCode());
return result;
}
#SuppressWarnings("rawtypes")
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ObjectMap other = (ObjectMap) obj;
if (map == null) {
if (other.map != null)
return false;
} else if (!map.equals(other.map))
return false;
return true;
}
}
and a gson adapter for it ObjectMapAdapter.
//ObjectMapAdapter.java
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.$Gson$Types;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
public class ObjectMapAdapter<V> extends TypeAdapter<ObjectMap<V>> {
public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
#SuppressWarnings({ "unchecked", "rawtypes" })
public <T> TypeAdapter create(Gson gson, TypeToken<T> typeToken) {
Type type = typeToken.getType();
Class<? super T> rawType = typeToken.getRawType();
if (!ObjectMap.class.isAssignableFrom(rawType)) {
return null;
}
// if (rawType != ObjectMap.class) {
// return null;
// }
Type componentType;
if (type instanceof ParameterizedType) {
componentType = ((ParameterizedType) type).getActualTypeArguments()[0];
} else {
componentType = Object.class;
}
TypeAdapter<?> componentTypeAdapter = gson.getAdapter(TypeToken.get(componentType));
return new ObjectMapAdapter(gson, componentTypeAdapter, $Gson$Types.getRawType(componentType));
}
};
// private final Class<V> valueType;
private final TypeAdapter<V> valueTypeAdapter;
public ObjectMapAdapter(Gson context, TypeAdapter<V> componentTypeAdapter, Class<V> componentType) {
this.valueTypeAdapter = new TypeAdapterRuntimeTypeWrapper<V>(context, componentTypeAdapter, componentType);
// this.valueType = componentType;
}
public ObjectMap<V> read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
Map<String, V> map = new LinkedHashMap<String, V>();
in.beginObject();
while (in.hasNext()) {
String key = in.nextName();
V comp = valueTypeAdapter.read(in);
map.put(key, comp);
}
in.endObject();
return new ObjectMap<V>(map);
}
#Override
public void write(JsonWriter out, ObjectMap<V> map) throws IOException {
if (map == null) {
out.nullValue();
return;
}
out.beginObject();
for (Entry<String, V> entry : map.getMap().entrySet()) {
out.name(entry.getKey());
valueTypeAdapter.write(out, entry.getValue());
}
out.endObject();
}
}
Create a custom GSON builder and register this factory
gsonBuilder.registerTypeAdapterFactory(ObjectMapAdapter.FACTORY);
the you are ready to decode data like this
{"a": "alpha", "2": "two", "3":3}
into a map
ObjectMap [map={a=alpha, 2=two, 3=3}]

Categories