Related
I have the following JSON scalar:
"""
The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).
"""
scalar JSON
which I am trying to convert since my query is accepting input: JSON. When testing using graphql playground, query is JSON object thus the following works:
query {
carts(where: {
owner:{id: "xxx"}
store:{name: "yyy"}
}) {
id
}
}
# query is the starting from the where: {...}
# build.yaml
# build.yaml
gql_build|schema_builder: #same for gql_build|schema_builder + gql_build|var_builder + ferry_generator|req_builder:
options:
type_overrides:
DateTime:
name: DateTime
JSON:
name: BuiltMap<String, dynamic>
import: 'package:built_collection/built_collection.dart'
gql_build|serializer_builder:
enabled: true
options:
schema: myapp|lib/graphql/schema.graphql
custom_serializers:
- import: 'package:myapp/app/utils/builtmapjson_serializer.dart'
name: BuiltMapJsonSerializer
This is the custom serializer (builtmapjson_serializer.dart)
//// lib/app/utils/builtmapjson_serializer.dart
import 'package:built_collection/built_collection.dart';
import "package:gql_code_builder/src/serializers/json_serializer.dart";
class BuiltMapJsonSerializer extends JsonSerializer<BuiltMap<String, dynamic>> {
#override
BuiltMap<String, dynamic> fromJson(Map<String, dynamic> json) {
print('MyJsonSerializer fromJson: $json');
return BuiltMap.of(json);
}
#override
Map<String, dynamic> toJson(BuiltMap<String, dynamic> operation) {
print('MyJsonSerializer toJson: ${operation.toString()}');
return operation.asMap();
}
}
and the usage:
Future testQuery() async {
Map<String, dynamic> queryMap = {
"where": {
"owner": {
"id": "xxx",
"store": {"name": "yyy"}
}
}
};
final req = GFindCartsReq((b) {
return b..vars.query.addAll(queryMap);
});
var resStream = _graphQLService.client.request(req);
var res = await resStream.first;
print(
'linkExceptions: ${res.linkException}'); // Map: LinkException(Bad state: No serializer for '_InternalLinkedHashMap<String, Map<String, Object>>'.)
}
So whenever I try to query, it is throwing the linkException stated in the comment on the last line of usage. Any idea what should be the way of serializing it?
// Write query like this
query FindCarts($owner_id: String!, $store_name: String!) {
carts(where: {
owner:{id: $owner_id}
store:{name: $store_name}
}) {
id
}
}
// And make request like this:
final req = GFindCartsReq((b) => b..vars.store_name = 'XXX'..vars.owner_id = 'YYY');
I think you may be misunderstanding the use case. they are there to serialize and deserialize the response if you want to end up with a Dart object that's different from graphql representation. you may want to try rereading this section:
https://ferrygraphql.com/docs/custom-scalars/#create-a-custom-serializer
in the example in the docs, the graphql schema returns an int for the timestamp, but we want to actually use a Date object, so that's the purpose of the serializer. it tells ferry to deserialize the int in our responses to a Date so we can use a Date in our dart code. you could still use a json serializer (like in the examples you linked to) but it still would not be in the way you're trying to use it -- it would be if your schema returns a json string and you want to deserialize the json string. for example, in my case, my graphql schema actually does return a "jsonb" type on some objects. in order to handle that, i'm using built_value's default json_object like this:
(
...
type_overrides:
jsonb:
name: JsonObject
import: "package:built_value/json_object.dart"
custom_serializers:
- import: "package:built_value/src/json_object_serializer.dart"
name: JsonObjectSerializer
I have a simple json file like below. This is fixed in the project.
{
"en": "English",
"es": "Español",
"fr": "Français"
}
My question is, is it possible to do a auto-generate work to create a class with members which return the value of each key? The result of the auto-generate can be similar to below example. So every time I add new key and value to the json file, this class will generate or update that new item into it.
class JsonHelper {
static String get en => json['en'];
static String get es => json['es'];
static String get fr => json['fr'];
}
you can use this site for Auto-generate class in Flutter:
https://app.quicktype.io/?share=pxoAci2ZiiPtLyjXaYhD
another way is using Auto-generator package of npm
New Edit: I just found this site: https://jsontodart.com/
Yes for sure you can use Auto-generate class in Flutter on this website: Instantly parse Json
Example
// To parse this JSON data, do
//
// final autoGenerate = autoGenerateFromJson(jsonString);
import 'dart:convert';
AutoGenerate autoGenerateFromJson(String str) => AutoGenerate.fromJson(json.decode(str));
String autoGenerateToJson(AutoGenerate data) => json.encode(data.toJson());
class AutoGenerate {
AutoGenerate({
this.en,
this.es,
this.fr,
});
String en;
String es;
String fr;
factory AutoGenerate.fromJson(Map<String, dynamic> json) => AutoGenerate(
en: json["en"],
es: json["es"],
fr: json["fr"],
);
Map<String, dynamic> toJson() => {
"en": en,
"es": es,
"fr": fr,
};
}
you can then simply use final objectFromClass = autoGenerateFromJson(jsonString)
how to add type information in Json serialization in Angular 7
Hello everybody, I need to add type information at Json serialization in Angular 7 in order to get this result (adding the line "$type": "Person",):
{
"$type": "Person",
"id": 8,
"firstName": "Anke",
"lastName": "Winkler",
…
}
I need this in order to deserialize in C# with Json.NET and knowing which type to use in case of properties that are typed with an interface and thus may be of different types. Thanks a lot for any advice!
It is not much an Angular serialization issue, but more a TypeScript/JavaScript issue.
You can customize your serialization by overriding the toJSON() method in your TypeScript classes.
class User {
public id: number;
public firstName: string;
public lastName: string;
public age: number;
public toJSON(): User {
return Object.assign({}, this, {
$type: 'User'
});
}
}
What the toJSON() method will do, is simply create a new object using the current one, and the add the $type property to it. It will be called when you call the JSON.stringify() method. Thus, you don't need to create a $type variable in your class.
Example:
const newUser: User = new User();
newUser.id = 8;
newUser.firstName = "John";
newUser.lastName = "Doe";
newUser.age = 42;
const newUserAsJson: string = JSON.stringify(newUser);
console.log(newUserAsJson);
// Displays:
// {firstName: "John", lastName: "Doe", id: 8, age: 42, $type: "User"}
Hope it helps.
I read a JSON object from a remote REST server. This JSON object has all the properties of a typescript class (by design). How do I cast that received JSON object to a type var?
I don't want to populate a typescript var (ie have a constructor that takes this JSON object). It's large and copying everything across sub-object by sub-object & property by property would take a lot of time.
Update: You can however cast it to a typescript interface!
You can't simple cast a plain-old-JavaScript result from an Ajax request into a prototypical JavaScript/TypeScript class instance. There are a number of techniques for doing it, and generally involve copying data. Unless you create an instance of the class, it won't have any methods or properties. It will remain a simple JavaScript object.
While if you only were dealing with data, you could just do a cast to an interface (as it's purely a compile time structure), this would require that you use a TypeScript class which uses the data instance and performs operations with that data.
Some examples of copying the data:
Copying AJAX JSON object into existing Object
Parse JSON String into a Particular Object Prototype in JavaScript
In essence, you'd just :
var d = new MyRichObject();
d.copyInto(jsonResult);
I had the same issue and I have found a library that does the job : https://github.com/pleerock/class-transformer.
It works like this :
let jsonObject = response.json() as Object;
let fooInstance = plainToClass(Models.Foo, jsonObject);
return fooInstance;
It supports nested children but you have to decorate your class's member.
In TypeScript you can do a type assertion using an interface and generics like so:
var json = Utilities.JSONLoader.loadFromFile("../docs/location_map.json");
var locations: Array<ILocationMap> = JSON.parse(json).location;
Where ILocationMap describes the shape of your data. The advantage of this method is that your JSON could contain more properties but the shape satisfies the conditions of the interface.
However, this does NOT add class instance methods.
If you are using ES6, try this:
class Client{
name: string
displayName(){
console.log(this.name)
}
}
service.getClientFromAPI().then(clientData => {
// Here the client data from API only have the "name" field
// If we want to use the Client class methods on this data object we need to:
let clientWithType = Object.assign(new Client(), clientData)
clientWithType.displayName()
})
But this method will not work on nested objects, sadly.
I found a very interesting article on generic casting of JSON to a Typescript Class:
http://cloudmark.github.io/Json-Mapping/
You end up with following code:
let example = {
"name": "Mark",
"surname": "Galea",
"age": 30,
"address": {
"first-line": "Some where",
"second-line": "Over Here",
"city": "In This City"
}
};
MapUtils.deserialize(Person, example); // custom class
There is nothing yet to automatically check if the JSON object you received from the server has the expected (read is conform to the) typescript's interface properties. But you can use User-Defined Type Guards
Considering the following interface and a silly json object (it could have been any type):
interface MyInterface {
key: string;
}
const json: object = { "key": "value" }
Three possible ways:
A. Type Assertion or simple static cast placed after the variable
const myObject: MyInterface = json as MyInterface;
B. Simple static cast, before the variable and between diamonds
const myObject: MyInterface = <MyInterface>json;
C. Advanced dynamic cast, you check yourself the structure of the object
function isMyInterface(json: any): json is MyInterface {
// silly condition to consider json as conform for MyInterface
return typeof json.key === "string";
}
if (isMyInterface(json)) {
console.log(json.key)
}
else {
throw new Error(`Expected MyInterface, got '${json}'.`);
}
You can play with this example here
Note that the difficulty here is to write the isMyInterface function. I hope TS will add a decorator sooner or later to export complex typing to the runtime and let the runtime check the object's structure when needed. For now, you could either use a json schema validator which purpose is approximately the same OR this runtime type check function generator
TLDR: One liner
// This assumes your constructor method will assign properties from the arg.
.map((instanceData: MyClass) => new MyClass(instanceData));
The Detailed Answer
I would not recommend the Object.assign approach, as it can inappropriately litter your class instance with irrelevant properties (as well as defined closures) that were not declared within the class itself.
In the class you are trying to deserialize into, I would ensure any properties you want deserialized are defined (null, empty array, etc). By defining your properties with initial values you expose their visibility when trying to iterate class members to assign values to (see deserialize method below).
export class Person {
public name: string = null;
public favoriteSites: string[] = [];
private age: number = null;
private id: number = null;
private active: boolean;
constructor(instanceData?: Person) {
if (instanceData) {
this.deserialize(instanceData);
}
}
private deserialize(instanceData: Person) {
// Note this.active will not be listed in keys since it's declared, but not defined
const keys = Object.keys(this);
for (const key of keys) {
if (instanceData.hasOwnProperty(key)) {
this[key] = instanceData[key];
}
}
}
}
In the example above, I simply created a deserialize method. In a real world example, I would have it centralized in a reusable base class or service method.
Here is how to utilize this in something like an http resp...
this.http.get(ENDPOINT_URL)
.map(res => res.json())
.map((resp: Person) => new Person(resp) ) );
If tslint/ide complains about argument type being incompatible, just cast the argument into the same type using angular brackets <YourClassName>, example:
const person = new Person(<Person> { name: 'John', age: 35, id: 1 });
If you have class members that are of a specific type (aka: instance of another class), then you can have them casted into typed instances through getter/setter methods.
export class Person {
private _acct: UserAcct = null;
private _tasks: Task[] = [];
// ctor & deserialize methods...
public get acct(): UserAcct {
return this.acct;
}
public set acct(acctData: UserAcct) {
this._acct = new UserAcct(acctData);
}
public get tasks(): Task[] {
return this._tasks;
}
public set tasks(taskData: Task[]) {
this._tasks = taskData.map(task => new Task(task));
}
}
The above example will deserialize both acct and the list of tasks into their respective class instances.
Assuming the json has the same properties as your typescript class, you don't have to copy your Json properties to your typescript object. You will just have to construct your Typescript object passing the json data in the constructor.
In your ajax callback, you receive a company:
onReceiveCompany( jsonCompany : any )
{
let newCompany = new Company( jsonCompany );
// call the methods on your newCompany object ...
}
In in order to to make that work:
1) Add a constructor in your Typescript class that takes the json data as parameter. In that constructor you extend your json object with jQuery, like this: $.extend( this, jsonData). $.extend allows keeping the javascript prototypes while adding the json object's properties.
2) Note you will have to do the same for linked objects. In the case of Employees in the example, you also create a constructor taking the portion of the json data for employees. You call $.map to translate json employees to typescript Employee objects.
export class Company
{
Employees : Employee[];
constructor( jsonData: any )
{
$.extend( this, jsonData);
if ( jsonData.Employees )
this.Employees = $.map( jsonData.Employees , (emp) => {
return new Employee ( emp ); });
}
}
export class Employee
{
name: string;
salary: number;
constructor( jsonData: any )
{
$.extend( this, jsonData);
}
}
This is the best solution I found when dealing with Typescript classes and json objects.
In my case it works. I used functions
Object.assign (target, sources ...).
First, the creation of the correct object, then copies the data from json object to the target.Example :
let u:User = new User();
Object.assign(u , jsonUsers);
And a more advanced example of use. An example using the array.
this.someService.getUsers().then((users: User[]) => {
this.users = [];
for (let i in users) {
let u:User = new User();
Object.assign(u , users[i]);
this.users[i] = u;
console.log("user:" + this.users[i].id);
console.log("user id from function(test it work) :" + this.users[i].getId());
}
});
export class User {
id:number;
name:string;
fullname:string;
email:string;
public getId(){
return this.id;
}
}
While it is not casting per se; I have found https://github.com/JohnWhiteTB/TypedJSON to be a useful alternative.
#JsonObject
class Person {
#JsonMember
firstName: string;
#JsonMember
lastName: string;
public getFullname() {
return this.firstName + " " + this.lastName;
}
}
var person = TypedJSON.parse('{ "firstName": "John", "lastName": "Doe" }', Person);
person instanceof Person; // true
person.getFullname(); // "John Doe"
Personally I find it appalling that typescript does not allow an endpoint definition to specify
the type of the object being received. As it appears that this is indeed the case,
I would do what I have done with other languages, and that is that I would separate the JSON object from the class definition,
and have the class definition use the JSON object as its only data member.
I despise boilerplate code, so for me it is usually a matter of getting to the desired result with the least amount of code while preserving type.
Consider the following JSON object structure definitions - these would be what you would receive at an endpoint, they are structure definitions only, no methods.
interface IAddress {
street: string;
city: string;
state: string;
zip: string;
}
interface IPerson {
name: string;
address: IAddress;
}
If we think of the above in object oriented terms, the above interfaces are not classes because they only define a data structure.
A class in OO terms defines data and the code that operates on it.
So we now define a class that specifies data and the code that operates on it...
class Person {
person: IPerson;
constructor(person: IPerson) {
this.person = person;
}
// accessors
getName(): string {
return person.name;
}
getAddress(): IAddress {
return person.address;
}
// You could write a generic getter for any value in person,
// no matter how deep, by accepting a variable number of string params
// methods
distanceFrom(address: IAddress): float {
// Calculate distance from the passed address to this persons IAddress
return 0.0;
}
}
And now we can simply pass in any object conforming to the IPerson structure and be on our way...
Person person = new Person({
name: "persons name",
address: {
street: "A street address",
city: "a city",
state: "a state",
zip: "A zipcode"
}
});
In the same fashion we can now process the object received at your endpoint with something along the lines of...
Person person = new Person(req.body); // As in an object received via a POST call
person.distanceFrom({ street: "Some street address", etc.});
This is much more performant and uses half the memory of copying the data, while significantly reducing the amount of boilerplate code you must write for each entity type.
It simply relies on the type safety provided by TypeScript.
Use a class extended from an interface.
Then:
Object.assign(
new ToWhat(),
what
)
And best:
Object.assign(
new ToWhat(),
<IDataInterface>what
)
ToWhat becomes a controller of DataInterface
If you need to cast your json object to a typescript class and have its instance methods available in the resulting object you need to use Object.setPrototypeOf, like I did in the code snippet bellow:
Object.setPrototypeOf(jsonObject, YourTypescriptClass.prototype)
Use 'as' declaration:
const data = JSON.parse(response.data) as MyClass;
An old question with mostly correct, but not very efficient answers. This what I propose:
Create a base class that contains init() method and static cast methods (for a single object and an array). The static methods could be anywhere; the version with the base class and init() allows easy extensions afterwards.
export class ContentItem {
// parameters: doc - plain JS object, proto - class we want to cast to (subclass of ContentItem)
static castAs<T extends ContentItem>(doc: T, proto: typeof ContentItem): T {
// if we already have the correct class skip the cast
if (doc instanceof proto) { return doc; }
// create a new object (create), and copy over all properties (assign)
const d: T = Object.create(proto.prototype);
Object.assign(d, doc);
// reason to extend the base class - we want to be able to call init() after cast
d.init();
return d;
}
// another method casts an array
static castAllAs<T extends ContentItem>(docs: T[], proto: typeof ContentItem): T[] {
return docs.map(d => ContentItem.castAs(d, proto));
}
init() { }
}
Similar mechanics (with assign()) have been mentioned in #Adam111p post. Just another (more complete) way to do it. #Timothy Perez is critical of assign(), but imho it is fully appropriate here.
Implement a derived (the real) class:
import { ContentItem } from './content-item';
export class SubjectArea extends ContentItem {
id: number;
title: string;
areas: SubjectArea[]; // contains embedded objects
depth: number;
// method will be unavailable unless we use cast
lead(): string {
return '. '.repeat(this.depth);
}
// in case we have embedded objects, call cast on them here
init() {
if (this.areas) {
this.areas = ContentItem.castAllAs(this.areas, SubjectArea);
}
}
}
Now we can cast an object retrieved from service:
const area = ContentItem.castAs<SubjectArea>(docFromREST, SubjectArea);
All hierarchy of SubjectArea objects will have correct class.
A use case/example; create an Angular service (abstract base class again):
export abstract class BaseService<T extends ContentItem> {
BASE_URL = 'http://host:port/';
protected abstract http: Http;
abstract path: string;
abstract subClass: typeof ContentItem;
cast(source: T): T {
return ContentItem.castAs(source, this.subClass);
}
castAll(source: T[]): T[] {
return ContentItem.castAllAs(source, this.subClass);
}
constructor() { }
get(): Promise<T[]> {
const value = this.http.get(`${this.BASE_URL}${this.path}`)
.toPromise()
.then(response => {
const items: T[] = this.castAll(response.json());
return items;
});
return value;
}
}
The usage becomes very simple; create an Area service:
#Injectable()
export class SubjectAreaService extends BaseService<SubjectArea> {
path = 'area';
subClass = SubjectArea;
constructor(protected http: Http) { super(); }
}
get() method of the service will return a Promise of an array already cast as SubjectArea objects (whole hierarchy)
Now say, we have another class:
export class OtherItem extends ContentItem {...}
Creating a service that retrieves data and casts to the correct class is as simple as:
#Injectable()
export class OtherItemService extends BaseService<OtherItem> {
path = 'other';
subClass = OtherItem;
constructor(protected http: Http) { super(); }
}
You can create an interface of your type (SomeType) and cast the object in that.
const typedObject: SomeType = <SomeType> responseObject;
FOR JAVA LOVERS
Make POJO class
export default class TransactionDTO{
constructor() {
}
}
create empty object by class
let dto = new TransactionDto() // ts object
let json = {name:"Kamal",age:40} // js object
let transaction: TransactionDto = Object.assign(dto,JSON.parse(JSON.stringify(json)));//conversion
https://jvilk.com/MakeTypes/
you can use this site to generate a proxy for you. it generates a class and can parse and validate your input JSON object.
I used this library here: https://github.com/pleerock/class-transformer
<script lang="ts">
import { plainToClass } from 'class-transformer';
</script>
Implementation:
private async getClassTypeValue() {
const value = await plainToClass(ProductNewsItem, JSON.parse(response.data));
}
Sometimes you will have to parse the JSON values for plainToClass to understand that it is a JSON formatted data
In the lates TS you can do like this:
const isMyInterface = (val: any): val is MyInterface => {
if (!val) { return false; }
if (!val.myProp) { return false; }
return true;
};
And than user like this:
if (isMyInterface(data)) {
// now data will be type of MyInterface
}
I ran into a similar need.
I wanted something that will give me easy transformation from/to JSON
that is coming from a REST api call to/from specific class definition.
The solutions that I've found were insufficient or meant to rewrite my
classes' code and adding annotations or similars.
I wanted something like GSON is used in Java to serialize/deserialize classes to/from JSON objects.
Combined with a later need, that the converter will function in JS as well, I ended writing my own package.
It has though, a little bit of overhead. But when started it is very convenient in adding and editing.
You initialize the module with :
conversion schema - allowing to map between fields and determine
how the conversion will be done
Classes map array
Conversion functions map - for special conversions.
Then in your code, you use the initialized module like :
const convertedNewClassesArray : MyClass[] = this.converter.convert<MyClass>(jsonObjArray, 'MyClass');
const convertedNewClass : MyClass = this.converter.convertOneObject<MyClass>(jsonObj, 'MyClass');
or , to JSON :
const jsonObject = this.converter.convertToJson(myClassInstance);
Use this link to the npm package and also a detailed explanation to how to work with the module: json-class-converter
Also wrapped it for
Angular use in :
angular-json-class-converter
Pass the object as is to the class constructor; No conventions or checks
interface iPerson {
name: string;
age: number;
}
class Person {
constructor(private person: iPerson) { }
toString(): string {
return this.person.name + ' is ' + this.person.age;
}
}
// runs this as //
const object1 = { name: 'Watson1', age: 64 };
const object2 = { name: 'Watson2' }; // age is missing
const person1 = new Person(object1);
const person2 = new Person(object2 as iPerson); // now matches constructor
console.log(person1.toString()) // Watson1 is 64
console.log(person2.toString()) // Watson2 is undefined
You can use this npm package. https://www.npmjs.com/package/class-converter
It is easy to use, for example:
class UserModel {
#property('i')
id: number;
#property('n')
name: string;
}
const userRaw = {
i: 1234,
n: 'name',
};
// use toClass to convert plain object to class
const userModel = toClass(userRaw, UserModel);
// you will get a class, just like below one
// const userModel = {
// id: 1234,
// name: 'name',
// }
You can with a single tapi.js!
It's a lightweight automapper that works in both ways.
npm i -D tapi.js
Then you can simply do
let typedObject = new YourClass().fromJSON(jsonData)
or with promises
axios.get(...).as(YourClass).then(typedObject => { ... })
You can read more about it on the docs.
There are several ways to do it, lets examine a some options:
class Person {
id: number | undefined;
firstName: string | undefined;
//? mark for note not required attribute.
lastName?: string;
}
// Option 1: Fill any attribute and it would be accepted.
const person1= { firstName: 'Cassio' } as Person ;
console.log(person1);
// Option 2. All attributes must assign data.
const person2: Person = { id: 1, firstName: 'Cassio', lastName:'Seffrin' };
console.log(person2);
// Option 3. Use partial interface if all attribute not required.
const person3: Partial<Person> = { firstName: 'Cassio' };
console.log(person3);
// Option 4. As lastName is optional it will work
const person4: Person = { id:2, firstName: 'Cassio' };
console.log(person4);
// Option 5. Fill any attribute and it would be accepted.
const person5 = <Person> {firstName: 'Cassio'};
console.log(person5 );
Result:
[LOG]: {
"firstName": "Cassio"
}
[LOG]: {
"id": 1,
"firstName": "Cassio",
"lastName": "Seffrin"
}
[LOG]: {
"firstName": "Cassio"
}
[LOG]: {
"id": 2,
"firstName": "Cassio"
}
[LOG]: {
"firstName": "Cassio"
}
It will also work if you have an interface instead a Typescript class.
interface PersonInterface {
id: number;
firstName: string;
lastName?: string;
}
Play this code
I think that json2typescript is a good alternative
https://www.npmjs.com/package/json2typescript
You can convert json to Class model with a simple model class with annotations
Used in project
You can cast json to property like this
class Jobs {
constructor(JSONdata) {
this.HEAT = JSONdata.HEAT;
this.HEAT_EAF = JSONdata.HEAT_EAF;
}
}
var job = new Jobs({HEAT:'123',HEAT_EAF:'456'});
This is a simple and a really good option
let person = "{"name":"Sam","Age":"30"}";
const jsonParse: ((key: string, value: any) => any) | undefined = undefined;
let objectConverted = JSON.parse(textValue, jsonParse);
And then you'll have
objectConverted.name
I read a JSON object from a remote REST server. This JSON object has all the properties of a typescript class (by design). How do I cast that received JSON object to a type var?
I don't want to populate a typescript var (ie have a constructor that takes this JSON object). It's large and copying everything across sub-object by sub-object & property by property would take a lot of time.
Update: You can however cast it to a typescript interface!
You can't simple cast a plain-old-JavaScript result from an Ajax request into a prototypical JavaScript/TypeScript class instance. There are a number of techniques for doing it, and generally involve copying data. Unless you create an instance of the class, it won't have any methods or properties. It will remain a simple JavaScript object.
While if you only were dealing with data, you could just do a cast to an interface (as it's purely a compile time structure), this would require that you use a TypeScript class which uses the data instance and performs operations with that data.
Some examples of copying the data:
Copying AJAX JSON object into existing Object
Parse JSON String into a Particular Object Prototype in JavaScript
In essence, you'd just :
var d = new MyRichObject();
d.copyInto(jsonResult);
I had the same issue and I have found a library that does the job : https://github.com/pleerock/class-transformer.
It works like this :
let jsonObject = response.json() as Object;
let fooInstance = plainToClass(Models.Foo, jsonObject);
return fooInstance;
It supports nested children but you have to decorate your class's member.
In TypeScript you can do a type assertion using an interface and generics like so:
var json = Utilities.JSONLoader.loadFromFile("../docs/location_map.json");
var locations: Array<ILocationMap> = JSON.parse(json).location;
Where ILocationMap describes the shape of your data. The advantage of this method is that your JSON could contain more properties but the shape satisfies the conditions of the interface.
However, this does NOT add class instance methods.
If you are using ES6, try this:
class Client{
name: string
displayName(){
console.log(this.name)
}
}
service.getClientFromAPI().then(clientData => {
// Here the client data from API only have the "name" field
// If we want to use the Client class methods on this data object we need to:
let clientWithType = Object.assign(new Client(), clientData)
clientWithType.displayName()
})
But this method will not work on nested objects, sadly.
I found a very interesting article on generic casting of JSON to a Typescript Class:
http://cloudmark.github.io/Json-Mapping/
You end up with following code:
let example = {
"name": "Mark",
"surname": "Galea",
"age": 30,
"address": {
"first-line": "Some where",
"second-line": "Over Here",
"city": "In This City"
}
};
MapUtils.deserialize(Person, example); // custom class
There is nothing yet to automatically check if the JSON object you received from the server has the expected (read is conform to the) typescript's interface properties. But you can use User-Defined Type Guards
Considering the following interface and a silly json object (it could have been any type):
interface MyInterface {
key: string;
}
const json: object = { "key": "value" }
Three possible ways:
A. Type Assertion or simple static cast placed after the variable
const myObject: MyInterface = json as MyInterface;
B. Simple static cast, before the variable and between diamonds
const myObject: MyInterface = <MyInterface>json;
C. Advanced dynamic cast, you check yourself the structure of the object
function isMyInterface(json: any): json is MyInterface {
// silly condition to consider json as conform for MyInterface
return typeof json.key === "string";
}
if (isMyInterface(json)) {
console.log(json.key)
}
else {
throw new Error(`Expected MyInterface, got '${json}'.`);
}
You can play with this example here
Note that the difficulty here is to write the isMyInterface function. I hope TS will add a decorator sooner or later to export complex typing to the runtime and let the runtime check the object's structure when needed. For now, you could either use a json schema validator which purpose is approximately the same OR this runtime type check function generator
TLDR: One liner
// This assumes your constructor method will assign properties from the arg.
.map((instanceData: MyClass) => new MyClass(instanceData));
The Detailed Answer
I would not recommend the Object.assign approach, as it can inappropriately litter your class instance with irrelevant properties (as well as defined closures) that were not declared within the class itself.
In the class you are trying to deserialize into, I would ensure any properties you want deserialized are defined (null, empty array, etc). By defining your properties with initial values you expose their visibility when trying to iterate class members to assign values to (see deserialize method below).
export class Person {
public name: string = null;
public favoriteSites: string[] = [];
private age: number = null;
private id: number = null;
private active: boolean;
constructor(instanceData?: Person) {
if (instanceData) {
this.deserialize(instanceData);
}
}
private deserialize(instanceData: Person) {
// Note this.active will not be listed in keys since it's declared, but not defined
const keys = Object.keys(this);
for (const key of keys) {
if (instanceData.hasOwnProperty(key)) {
this[key] = instanceData[key];
}
}
}
}
In the example above, I simply created a deserialize method. In a real world example, I would have it centralized in a reusable base class or service method.
Here is how to utilize this in something like an http resp...
this.http.get(ENDPOINT_URL)
.map(res => res.json())
.map((resp: Person) => new Person(resp) ) );
If tslint/ide complains about argument type being incompatible, just cast the argument into the same type using angular brackets <YourClassName>, example:
const person = new Person(<Person> { name: 'John', age: 35, id: 1 });
If you have class members that are of a specific type (aka: instance of another class), then you can have them casted into typed instances through getter/setter methods.
export class Person {
private _acct: UserAcct = null;
private _tasks: Task[] = [];
// ctor & deserialize methods...
public get acct(): UserAcct {
return this.acct;
}
public set acct(acctData: UserAcct) {
this._acct = new UserAcct(acctData);
}
public get tasks(): Task[] {
return this._tasks;
}
public set tasks(taskData: Task[]) {
this._tasks = taskData.map(task => new Task(task));
}
}
The above example will deserialize both acct and the list of tasks into their respective class instances.
Assuming the json has the same properties as your typescript class, you don't have to copy your Json properties to your typescript object. You will just have to construct your Typescript object passing the json data in the constructor.
In your ajax callback, you receive a company:
onReceiveCompany( jsonCompany : any )
{
let newCompany = new Company( jsonCompany );
// call the methods on your newCompany object ...
}
In in order to to make that work:
1) Add a constructor in your Typescript class that takes the json data as parameter. In that constructor you extend your json object with jQuery, like this: $.extend( this, jsonData). $.extend allows keeping the javascript prototypes while adding the json object's properties.
2) Note you will have to do the same for linked objects. In the case of Employees in the example, you also create a constructor taking the portion of the json data for employees. You call $.map to translate json employees to typescript Employee objects.
export class Company
{
Employees : Employee[];
constructor( jsonData: any )
{
$.extend( this, jsonData);
if ( jsonData.Employees )
this.Employees = $.map( jsonData.Employees , (emp) => {
return new Employee ( emp ); });
}
}
export class Employee
{
name: string;
salary: number;
constructor( jsonData: any )
{
$.extend( this, jsonData);
}
}
This is the best solution I found when dealing with Typescript classes and json objects.
In my case it works. I used functions
Object.assign (target, sources ...).
First, the creation of the correct object, then copies the data from json object to the target.Example :
let u:User = new User();
Object.assign(u , jsonUsers);
And a more advanced example of use. An example using the array.
this.someService.getUsers().then((users: User[]) => {
this.users = [];
for (let i in users) {
let u:User = new User();
Object.assign(u , users[i]);
this.users[i] = u;
console.log("user:" + this.users[i].id);
console.log("user id from function(test it work) :" + this.users[i].getId());
}
});
export class User {
id:number;
name:string;
fullname:string;
email:string;
public getId(){
return this.id;
}
}
While it is not casting per se; I have found https://github.com/JohnWhiteTB/TypedJSON to be a useful alternative.
#JsonObject
class Person {
#JsonMember
firstName: string;
#JsonMember
lastName: string;
public getFullname() {
return this.firstName + " " + this.lastName;
}
}
var person = TypedJSON.parse('{ "firstName": "John", "lastName": "Doe" }', Person);
person instanceof Person; // true
person.getFullname(); // "John Doe"
Personally I find it appalling that typescript does not allow an endpoint definition to specify
the type of the object being received. As it appears that this is indeed the case,
I would do what I have done with other languages, and that is that I would separate the JSON object from the class definition,
and have the class definition use the JSON object as its only data member.
I despise boilerplate code, so for me it is usually a matter of getting to the desired result with the least amount of code while preserving type.
Consider the following JSON object structure definitions - these would be what you would receive at an endpoint, they are structure definitions only, no methods.
interface IAddress {
street: string;
city: string;
state: string;
zip: string;
}
interface IPerson {
name: string;
address: IAddress;
}
If we think of the above in object oriented terms, the above interfaces are not classes because they only define a data structure.
A class in OO terms defines data and the code that operates on it.
So we now define a class that specifies data and the code that operates on it...
class Person {
person: IPerson;
constructor(person: IPerson) {
this.person = person;
}
// accessors
getName(): string {
return person.name;
}
getAddress(): IAddress {
return person.address;
}
// You could write a generic getter for any value in person,
// no matter how deep, by accepting a variable number of string params
// methods
distanceFrom(address: IAddress): float {
// Calculate distance from the passed address to this persons IAddress
return 0.0;
}
}
And now we can simply pass in any object conforming to the IPerson structure and be on our way...
Person person = new Person({
name: "persons name",
address: {
street: "A street address",
city: "a city",
state: "a state",
zip: "A zipcode"
}
});
In the same fashion we can now process the object received at your endpoint with something along the lines of...
Person person = new Person(req.body); // As in an object received via a POST call
person.distanceFrom({ street: "Some street address", etc.});
This is much more performant and uses half the memory of copying the data, while significantly reducing the amount of boilerplate code you must write for each entity type.
It simply relies on the type safety provided by TypeScript.
Use a class extended from an interface.
Then:
Object.assign(
new ToWhat(),
what
)
And best:
Object.assign(
new ToWhat(),
<IDataInterface>what
)
ToWhat becomes a controller of DataInterface
If you need to cast your json object to a typescript class and have its instance methods available in the resulting object you need to use Object.setPrototypeOf, like I did in the code snippet bellow:
Object.setPrototypeOf(jsonObject, YourTypescriptClass.prototype)
Use 'as' declaration:
const data = JSON.parse(response.data) as MyClass;
An old question with mostly correct, but not very efficient answers. This what I propose:
Create a base class that contains init() method and static cast methods (for a single object and an array). The static methods could be anywhere; the version with the base class and init() allows easy extensions afterwards.
export class ContentItem {
// parameters: doc - plain JS object, proto - class we want to cast to (subclass of ContentItem)
static castAs<T extends ContentItem>(doc: T, proto: typeof ContentItem): T {
// if we already have the correct class skip the cast
if (doc instanceof proto) { return doc; }
// create a new object (create), and copy over all properties (assign)
const d: T = Object.create(proto.prototype);
Object.assign(d, doc);
// reason to extend the base class - we want to be able to call init() after cast
d.init();
return d;
}
// another method casts an array
static castAllAs<T extends ContentItem>(docs: T[], proto: typeof ContentItem): T[] {
return docs.map(d => ContentItem.castAs(d, proto));
}
init() { }
}
Similar mechanics (with assign()) have been mentioned in #Adam111p post. Just another (more complete) way to do it. #Timothy Perez is critical of assign(), but imho it is fully appropriate here.
Implement a derived (the real) class:
import { ContentItem } from './content-item';
export class SubjectArea extends ContentItem {
id: number;
title: string;
areas: SubjectArea[]; // contains embedded objects
depth: number;
// method will be unavailable unless we use cast
lead(): string {
return '. '.repeat(this.depth);
}
// in case we have embedded objects, call cast on them here
init() {
if (this.areas) {
this.areas = ContentItem.castAllAs(this.areas, SubjectArea);
}
}
}
Now we can cast an object retrieved from service:
const area = ContentItem.castAs<SubjectArea>(docFromREST, SubjectArea);
All hierarchy of SubjectArea objects will have correct class.
A use case/example; create an Angular service (abstract base class again):
export abstract class BaseService<T extends ContentItem> {
BASE_URL = 'http://host:port/';
protected abstract http: Http;
abstract path: string;
abstract subClass: typeof ContentItem;
cast(source: T): T {
return ContentItem.castAs(source, this.subClass);
}
castAll(source: T[]): T[] {
return ContentItem.castAllAs(source, this.subClass);
}
constructor() { }
get(): Promise<T[]> {
const value = this.http.get(`${this.BASE_URL}${this.path}`)
.toPromise()
.then(response => {
const items: T[] = this.castAll(response.json());
return items;
});
return value;
}
}
The usage becomes very simple; create an Area service:
#Injectable()
export class SubjectAreaService extends BaseService<SubjectArea> {
path = 'area';
subClass = SubjectArea;
constructor(protected http: Http) { super(); }
}
get() method of the service will return a Promise of an array already cast as SubjectArea objects (whole hierarchy)
Now say, we have another class:
export class OtherItem extends ContentItem {...}
Creating a service that retrieves data and casts to the correct class is as simple as:
#Injectable()
export class OtherItemService extends BaseService<OtherItem> {
path = 'other';
subClass = OtherItem;
constructor(protected http: Http) { super(); }
}
You can create an interface of your type (SomeType) and cast the object in that.
const typedObject: SomeType = <SomeType> responseObject;
FOR JAVA LOVERS
Make POJO class
export default class TransactionDTO{
constructor() {
}
}
create empty object by class
let dto = new TransactionDto() // ts object
let json = {name:"Kamal",age:40} // js object
let transaction: TransactionDto = Object.assign(dto,JSON.parse(JSON.stringify(json)));//conversion
https://jvilk.com/MakeTypes/
you can use this site to generate a proxy for you. it generates a class and can parse and validate your input JSON object.
I used this library here: https://github.com/pleerock/class-transformer
<script lang="ts">
import { plainToClass } from 'class-transformer';
</script>
Implementation:
private async getClassTypeValue() {
const value = await plainToClass(ProductNewsItem, JSON.parse(response.data));
}
Sometimes you will have to parse the JSON values for plainToClass to understand that it is a JSON formatted data
In the lates TS you can do like this:
const isMyInterface = (val: any): val is MyInterface => {
if (!val) { return false; }
if (!val.myProp) { return false; }
return true;
};
And than user like this:
if (isMyInterface(data)) {
// now data will be type of MyInterface
}
I ran into a similar need.
I wanted something that will give me easy transformation from/to JSON
that is coming from a REST api call to/from specific class definition.
The solutions that I've found were insufficient or meant to rewrite my
classes' code and adding annotations or similars.
I wanted something like GSON is used in Java to serialize/deserialize classes to/from JSON objects.
Combined with a later need, that the converter will function in JS as well, I ended writing my own package.
It has though, a little bit of overhead. But when started it is very convenient in adding and editing.
You initialize the module with :
conversion schema - allowing to map between fields and determine
how the conversion will be done
Classes map array
Conversion functions map - for special conversions.
Then in your code, you use the initialized module like :
const convertedNewClassesArray : MyClass[] = this.converter.convert<MyClass>(jsonObjArray, 'MyClass');
const convertedNewClass : MyClass = this.converter.convertOneObject<MyClass>(jsonObj, 'MyClass');
or , to JSON :
const jsonObject = this.converter.convertToJson(myClassInstance);
Use this link to the npm package and also a detailed explanation to how to work with the module: json-class-converter
Also wrapped it for
Angular use in :
angular-json-class-converter
Pass the object as is to the class constructor; No conventions or checks
interface iPerson {
name: string;
age: number;
}
class Person {
constructor(private person: iPerson) { }
toString(): string {
return this.person.name + ' is ' + this.person.age;
}
}
// runs this as //
const object1 = { name: 'Watson1', age: 64 };
const object2 = { name: 'Watson2' }; // age is missing
const person1 = new Person(object1);
const person2 = new Person(object2 as iPerson); // now matches constructor
console.log(person1.toString()) // Watson1 is 64
console.log(person2.toString()) // Watson2 is undefined
You can use this npm package. https://www.npmjs.com/package/class-converter
It is easy to use, for example:
class UserModel {
#property('i')
id: number;
#property('n')
name: string;
}
const userRaw = {
i: 1234,
n: 'name',
};
// use toClass to convert plain object to class
const userModel = toClass(userRaw, UserModel);
// you will get a class, just like below one
// const userModel = {
// id: 1234,
// name: 'name',
// }
You can with a single tapi.js!
It's a lightweight automapper that works in both ways.
npm i -D tapi.js
Then you can simply do
let typedObject = new YourClass().fromJSON(jsonData)
or with promises
axios.get(...).as(YourClass).then(typedObject => { ... })
You can read more about it on the docs.
There are several ways to do it, lets examine a some options:
class Person {
id: number | undefined;
firstName: string | undefined;
//? mark for note not required attribute.
lastName?: string;
}
// Option 1: Fill any attribute and it would be accepted.
const person1= { firstName: 'Cassio' } as Person ;
console.log(person1);
// Option 2. All attributes must assign data.
const person2: Person = { id: 1, firstName: 'Cassio', lastName:'Seffrin' };
console.log(person2);
// Option 3. Use partial interface if all attribute not required.
const person3: Partial<Person> = { firstName: 'Cassio' };
console.log(person3);
// Option 4. As lastName is optional it will work
const person4: Person = { id:2, firstName: 'Cassio' };
console.log(person4);
// Option 5. Fill any attribute and it would be accepted.
const person5 = <Person> {firstName: 'Cassio'};
console.log(person5 );
Result:
[LOG]: {
"firstName": "Cassio"
}
[LOG]: {
"id": 1,
"firstName": "Cassio",
"lastName": "Seffrin"
}
[LOG]: {
"firstName": "Cassio"
}
[LOG]: {
"id": 2,
"firstName": "Cassio"
}
[LOG]: {
"firstName": "Cassio"
}
It will also work if you have an interface instead a Typescript class.
interface PersonInterface {
id: number;
firstName: string;
lastName?: string;
}
Play this code
I think that json2typescript is a good alternative
https://www.npmjs.com/package/json2typescript
You can convert json to Class model with a simple model class with annotations
Used in project
You can cast json to property like this
class Jobs {
constructor(JSONdata) {
this.HEAT = JSONdata.HEAT;
this.HEAT_EAF = JSONdata.HEAT_EAF;
}
}
var job = new Jobs({HEAT:'123',HEAT_EAF:'456'});
This is a simple and a really good option
let person = "{"name":"Sam","Age":"30"}";
const jsonParse: ((key: string, value: any) => any) | undefined = undefined;
let objectConverted = JSON.parse(textValue, jsonParse);
And then you'll have
objectConverted.name