I defined the following class:
'''
import { Deserializable } from '../deserializable';
export class Outdoor implements Deserializable {
ActualTemp: number;
TargetTemp: number;
Day: number;
deserialize(input: any): this {
// Assign input to our object
if(input){
Object.assign(this, input);
}
return this;
}
toJSON() {
return Object.assign({}, this);
}
}
'''
The toJSON function can generate a class data JSON String using
'''
const resource = JSON.stringify(this.appEngineMsg.Outdoor.toJSON());
'''
{"ActualTemp":60, "TargetTemp":55,"Day":23}
If I change the class object's variable this.appEngineMsg.Outdoor.TargertTemp, this.appEngineMsg.Outdoor.ActualTemp, then I would like to get the following JSON strings
{"Outdoor":{"TargetTemp":100}}
{"Outdoor":{"ActualTemp":100}}
What is the best way to do this?
Your question is not clear, do you want to print 2 differents JSON objects for each property?
Related
I followed code from this example but my toJSON() function is not called.
Attempt 1
export class Template {
constructor(
) {}
public id: string
public account_id: string
public name: string
public title: string
public info: string
public template_items: Array<number>
public toJSON = function() {
return {
attributes: this.template_items
}
}
}
Attempt 2
interface ITemplateSerialized {
attributes: Array<number>
}
export class Template {
constructor(
) {}
public id: string
public account_id: string
public name: string
public title: string
public info: string
public template_items: Array<number>
toJSON(): ITemplateSerialized {
return {
attributes: this.template_items
}
}
}
Attempt 3
Identical code to Attempt 2 except the toJSON is:
public toJSON = function(): ITemplateSerialized {
return {
attributes: this.template_items
}
}
Create some data...eg:
let t = new Template();
t.name = "Mickey Mouse"
t.template_items = [1,2,3]
console.log(JSON.stringify(t));
In all cases it does not change template_items to attributes...what am I missing here?
UPDATE
The provided plunk by #estus in the comments worked, so I decided to make one in Angular, to compare. Here it is and it works.
When I wrote the question, to make the code simple to understand, I had made 'template_items' an array of numbers. But in my actual Angular project it is an array of custom objects. Here is a plunker showing that structure. It also works. And another plunker working in Angular 4.4.6
But this identical setup does not work in my Angular project. So the question stands in case anyone else can reproduce this?
In my project I get a completely empty object returned from JSON.stringify().
So, it seems I was confused between how toJSON() works and how stringify replacer function works.
With toJSON() the function you supply will ONLY return the items you specify. I was under the impression that it would return all properties and ONLY change the ones you specify in the function.
So in my project there were no template_items at the point the object was first created, and since that was the ONLY property my serialized interface specified all the other properties were being removed, hence an empty object.
So, the solution is to specify ALL properties, in both the function return statement and in the serialize interface:
toJSON(): ITemplateSerialized {
return {
id: this.id,
account_id: this.account_id,
name: this.name,
title: this.title,
info: this.info,
attributes: this.template_items
}
}
export interface ITemplateSerialized {
id: string,
account_id: string,
name: string,
title: string,
info: string,
attributes: Array<TemplateItem>
}
I have the following code which seems wrong:
public search(searchString: string): Observable<Array<ClientSearchResult>> {
let params = new HttpParams().set('searchString', searchString);
return this.http
.get<Array<ClientSearchResult>>(this.searchUrl, { params: params })
.map((results: ClientSearchResult[]) => results.map((r: ClientSearchResult) => new ClientSearchResult(r)));
}
I know that the API is returning a JSON object which is not the same as an instance of my TypeScript class. However, I want to use properties defined in the TypeScript class.
Is there a better way to map the array coming from my API call to an array that actually consists of instances of ClientSearchResult?
Here is the ClientSearchResult object:
import { Name } from './name';
export class ClientSearchResult {
public id: string;
public name: Name;
public dateOfBirth: Date;
public socialSecurityNumber: string;
public get summary(): string {
let result: string = `${this.name}`;
if (this.dateOfBirth)
result += ` | ${this.dateOfBirth.toLocaleDateString()}`;
return result;
}
constructor(source: ClientSearchResult) {
this.id = source.id;
this.name = new Name(source.name);
this.dateOfBirth = source.dateOfBirth? new Date(source.dateOfBirth) : undefined;
this.socialSecurityNumber = source.socialSecurityNumber;
}
public toString(): string {
return this.summary;
}
}
We use a wonderful library to map json to typescript objects.
https://github.com/shakilsiraj/json-object-mapper
json-object-mapper depends on reflect-metadata library as it is using decorators to serialize and deserialize the data.
As an option you may try TypeScript as operator to cast your API response to the ClientSearchResult type.
import { Http, Response } from '#angular/http';
public search(searchString: string): Observable<ClientSearchResult[]> {
const params = new HttpParams().set('searchString', searchString);
return this.http.get(this.searchUrl, { params: params })
.map((results: Response) => results.json() as ClientSearchResult[]);
}
This approach requires your model class to be used as an interface, or just to be an interface:
interface ClientSearchResult {
id: number;
// etc
}
I have been using this very nice (and up-to-date at the time of posting) library:
https://www.npmjs.com/package/class-transformer
It can handle very complex cases with nested classes and more.
Let's say I want to get a data from Visual Studio TFS and the response (as json) is in this kind of format:
{
"Microsoft.VSTS.Scheduling.StoryPoints": 3.0,
// ......
}
There's dot in the property name. Reading from other questions I found out that I can read that json in typescript by using an interface like this
export interface IStory { // I don't think this kind of interface do me any help
"Microsoft.VSTS.Scheduling.StoryPoints": number
}
And then I can use the property with this syntax:
var story = GetStoryFromTFS();
console.log(story["Microsoft.VSTS.Scheduling.StoryPoints"]);
But I'd prefer not to call the property like this, since the intellisense won't able to help me finding which property I want to use (because I call the property using a string).
In C# there is a JsonProperty attribute which enable me to create a model like this:
public class Story
{
[JsonProperty(PropertyName = "Microsoft.VSTS.Scheduling.StoryPoints")]
public double StoryPoints { get; set; }
}
And then I can use the property this way:
var story = GetStoryFromTFS();
Console.WriteLine(story.StoryPoints);
This way the intellisense will able to help me finding which property I want to use.
Is there something like JsonProperty attribute in typescript? Or is there any other, better way, to achieve this in typescript?
You have many options. Just keep in mind that all of these options require you to pass the original data to the class that will access it.
Map the values.
class StoryMap {
constructor(data: IStory) {
this.StoryPoints = data["Microsoft.VSTS.Scheduling.StoryPoints"];
}
StoryPoints: number;
}
Wrap the data.
class StoryWrap {
constructor(private data: IStory) {}
get StoryPoints(): number { return this.data["Microsoft.VSTS.Scheduling.StoryPoints"] };
}
Build a decorator to map the data.
function JsonProperty(name: string) {
return function DoJsonProperty(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.get = function () {
return this.data[name];
}
descriptor.set = function (value) {
this.data[name] = value;
}
}
}
class StoryDecorator
{
constructor(private data: IStory) {}
#JsonProperty("Microsoft.VSTS.Scheduling.StoryPoints")
get StoryPoints(): number { return 0 };
}
Example
class A {
constructor(public val1: number, public val2: number,public val3: string, public b: B) {
}
}
class B {
constructor(public val4: boolean, public val5: number) {
}
}
exists any function that receives class A and return the JSON structure of the class, not matter if it's just visual, return this:
{val1: number, val2: number, val3: string, b: {val4: boolean, val5: number}}
a class in typescript it's no more that a hash of {nameProperty:data type, ...}
I want the class that only have properties, has not methods.
I'll better explain what I wrote in my comment.
In typescript when you define a class you need to define the members in it, but when it's compiled to js the members aren't really being added to the class.
Consider the following typescript:
class A {
str: string;
num: number;
}
It compiles to:
var A = (function () {
function A() {
}
return A;
}());
As you can see there's no trace of the str and num members in the js code.
When you assign values:
class A {
constructor(public str: string, public num: number) {}
}
It compiles to:
var A = (function () {
function A(str, num) {
this.str = str;
this.num = num;
}
return A;
}());
Here you can see str and num, but notice that they are added to the instance in the constructor, they are not added to the prototype (unlike methods) or to A, so there's no way to access them until you have an instance.
Because of that you can not get a mapping of properties in a class.
You can use the reflect-metadata package to save the members meta data.
I am sending a json reponse from server in the following format:
{id: Int, name: String, childJSON: String}
and willing to map it to
export class Student{
constructor(public id: string,
public name: string,
public childJSON: ChildObject) {
}
export class ChildObject {
constructor(public class: number,
public age: number){}
on doing response.json() as Student; I am getting {id:1, name: "sumit", childJSON: "{class: 5, age: 10}" i.e. childJSON has string type instead of ChildObject type. Basically the string is not mapped to my child object. Is this the correct way to achieve it or i need to send child object from the server instead of just JSON String
You need to "re-hydrate" the objects manually in the constructor and you can't use the "parameter property" shortcut to do that (the technique where you used public in the constructor to automatically convert constructor params into class properties).
Here's how I would do it:
export class Student{
constructor(options: {
id: string;
name: string;
childJSON: any;
}) {
// Now you have to instantiate the class properties one by one.
this.id = options.id;
this.name = options.name;
this.childJSON = new ChildObject(options.childJSON);
}
}
And then to instantiate:
const student = new Student(studentJson);
Or, if you're using an Observable to fetch the data:
this.http.get(...).map(...).subscribe(studentJson =>
this.student = new Student(studentJson)
}
This solution is more flexible, as you can pass the original JSON object directly for instanciation. In your example, you had to write something like:
// NOT GOOD: You must pass each property individually, in the right order...
const student = new Student(studentJson.id, studentJson.name,...);