In here: https://hackernoon.com/import-json-into-typescript-8d465beded79, I read it is possible to import JSON objects to my TypeScript project. I did that with the following JSON:
{
"defaultLanguage": "en",
"languageMap": {
"en": "English",
"pl": "Polish",
"de": "German"
}
}
I now want to make sure that any future changes to this file do not break my application so I introduced a following interface to the imported object:
export default interface IConfig {
defaultLanguage: string;
languageMap: ILanguage
}
interface ILanguage {
[language: string]: string
}
I have changed the typings.d.ts file:
declare module "*.json" {
const value: any;
export default value;
}
And imported to my Angular component:
import IConfig from './IConfig'; // Tried to put IConfig
// as a type in multiple places.
import * as config from './config.json';
(...)
private languageMap = config.languageMap; // in this line my Visual Studio Code
// underlines languageMap and shows
// the following error:
[ts] Property 'languageMap' does not exist on type 'typeof "*.json"'.
And below:
any
I am wondering if there is a way not to use (<any>config).languageMap but to include my IConfig interface, as suggested in the link above.
The module *.json module defines a wilds card module that will match any file ending with json and will type the json value as any. You can define a more specific module for config.json.
You can place a config.json.d.ts along side the config.json file describing the json:
//config.json.d.ts
interface IConfig {
defaultLanguage: string;
languageMap: ILanguage
}
interface ILanguage {
[language: string]: string
}
declare const value: IConfig;
export = value;
// usage.ts
import * as config from './config.json';
var languageMap = config.defaultLanguage; // All ok
Or if you use a module system taht does not support export=value you can use this definitions:
//config.json.d.ts
export let defaultLanguage: string;
export let languageMap: ILanguage
interface ILanguage {
[language: string]: string
}
Related
In our project, we are using string enum, to send the data from the BE to the client. Now the issue is, that when we mock the data, it contains the "enum strings" in the mock json files.
These mock json files are then imported like this: import testJson from '.test.json'; ('resolveJsonModule' is enabled in tsconfig), which gives them a type 'based on the static JSON shape.' (See: Resolve JSON Module).
Now the type which is generated for that enum property is string and not enum, so when I try to cast the json to the interface type, it throws me an error: Type 'string' is not assignable to type 'testEnum'.
Stackblitz example:
const testJSON = {
name: 'hello World',
enumProp: "MYTESTENUM_2",
};
enum testEnum {
MYTESTENUM = "MYTESTENUM",
MYTESTENUM_2 = "MYTESTENUM_2",
}
interface testinterface {
name: string;
enumProp: testEnum;
}
//const castJsonFile: testinterface = testJSON; // not compiling because the "string enums" cannot be converted
const castJsonFile: testinterface = testJSON as any; // any cast is needed, to get it compiling, but looses all type safety
// Write TypeScript code!
const appDiv: HTMLElement = document.getElementById('app');
appDiv.innerHTML = (castJsonFile.enumProp === testEnum.MYTESTENUM_2) as any as string;
Link: https://stackblitz.com/edit/typescript-fjy8zs?file=index.ts
If I would not use "string enums" it would work fine, but this is a change which cannot be done.
Is there a way to tell typescript to treat the "string enums" as enums if he finds such a property in a JSON file?
I would like to add and populate additional fields (which are not sent by backend service) in my http model. Catch is that I am not able to populate (map) those fields in the place where http response is being received since I am using internal framework.
Is there a possibility in Typescript (Angular) to somehow override JSON Deserialisation flow/Instance creation and populate mentioned fields. For example:
interface ElectricDevice {
energy_meter_start: number; // received from backend service
energy_meter_stop: number; // received from backend service
energy_spent: number; // not received by backend service, but needs to be populated as energy_meter_stop - energy_meter_start
// ...
/* I would like to somehow populate energy_spent as energy_meter_stop-energy_meter_end on instance creation (deserialisation) */
}
You need a HttpInterceptor, in which you can manipulate data.
#Injectable()
export class CustomJsonInterceptor implements HttpInterceptor {
constructor(private jsonParser: JsonParser) {}
intercept(httpRequest: HttpRequest<any>, next: HttpHandler) {
if (httpRequest.responseType === 'json') {
// If the expected response type is JSON then handle it here.
return this.handleJsonResponse(httpRequest, next);
} else {
return next.handle(httpRequest);
}
}
Read more about it in the tutorials: https://angular.io/api/common/http/HttpInterceptor
I have asked you for the especific names of your services.
But, in the meantime, I give you a 'general' answer to your question.
You just need to do this:
this.yourService.yourGetElectriceDevices
.pipe(
map (_resp: ElectricDevice => _resp.energy_spent = _resp.energy_meter_stop - _resp.energy_meter_start
)
.subscribe( resp => { //your treatment to the response });
This above, only works for a rapid test.
If you want to do somethig more 'elaborated', you could transform your interface into a class, and add your calculated attribute, something like this:
export interface ElectricDevice {
energy_meter_start: number; // received from backend service
energy_meter_stop: number; // received from backend service
}
export Class ElectricDeviceClass {
energy_meter_start: number;
energy_meter_stop: number;
energy_spent: number;
constructor (data: ElectricDevice) {
this.energy_meter_start = data.energy_meter_start;
this.energy_meter_stop= data.energy_meter_stop;
this.energy_spent = this.energy_meter_stop - this.energy_meter_start;
}
And for using it, just:
import { ElectricDeviceClass, ElectricDevice } from './../model/...' // There where you have yours interfaces and model classes
this.yourService.yourGetElectriceDevices
.pipe(
map (_resp: ElectricDevice => new ElectricDeviceClass(_resp)
)
.subscribe( resp => { //your treatment to the response });
I am trying to map the Json response to a Typescript class in Angular.
Json Response: [{"id":1,"nodeName":"Root","parentNodeId":null,"value":100.0,"children":[]}]
Although when I run locally I don't see error but the list is still undefined when I try to print on stackblitz, I see below error:
error: SyntaxError: Unexpected token < in JSON at position 0 at JSON.parse (<anonymous>) at XMLHttpRequest.onLoad
Although I have checked that my Json on Json Lint it is a Valid Json.
Typescript Class:
export class Node {
id?: number;
nodeName?: string;
parentNodeId?: any;
value?: number;
children?: any[];
}
Component.ts Code:
public nodes: Node[];
constructor(private nodeService: NodeService) {}
getAllNodes() {
this.nodeService.getListOfNodes().subscribe((data) => {
console.log('response==>', data);
this.nodes = data;
});
console.log('nodes ====>', this.nodes);
}
StackBlitz Editor Url and StackBlitz Application Url
Try creating a new folder assets on the same level as your app folder, then move the api.json file into the asset folder.
Next, modify the node retrieval like this:
getListOfNodes(): Observable<Node[]> {
return this.httpClient.get<Node[]>('/assets/api.json')
}
The nodes should now load.
I have a class which is populated as such (from json):
export class blogEntry {
hasImage: boolean;
#newItem(Video) video: Video;
#newItem(Author) author: Author;
#newItem(Comments) comments: Comments;
#newItem(Picture) blogImage: Picture;
#newItem(PictureCaption) pictureCaption: PictureCaption;
#newItem(TextSummary) summary: TextSummary;
constructor(data: blogEntry|Object) {
Object.assign(this, data);
}
}
Where the decorator is defined as such (in a seperate file):
export function newItem<T extends EditablePart>(type) {
return function(target: Object, propertyKey) {
let value: T;
Object.defineProperty(target, propertyKey, {
get: function(): T {
return value;
},
set: function(newVal: T) {
if (newVal) {
value = construct(type, newVal);
}
},
enumerable: true
});
}
}
export function construct<T extends EditablePart>(type: { new(...args : any[]): T ;}, newVal) {
return new type(newVal);
}
All annotated types extend EditablePart.
After using this class to change the data (by using the annotated fields, i.e. via the getters/setters supplied there), i want to save the class data as json to my backend server. Before introducing the decorators in the class I could just use:
publish(): blogEntry {
return new blogEntry(this);
}
Now I only get hasImage. When using developer tools in chrome I can see the fields but I have to click the dots behind them ('invoke property getter') to retrieve the data.
Any thought how to clone the class (I want to continue using the class)? Any help would be greatly appreciated!
It occurs to me use JSON.stringify(...) and a custom function that helps you resolve directives before.
The function would be a replacer function according stringify documentation.
Example: I have a component I would like to save in BD. My component have a ViewChild decorator and I want to save it too. I know that my ViewChild is an ElementRef but I wouldn't like to save a structure like this:
{
nativeElement: {}
}
The above is resulted of my JSON.stringify(...) function when it gets my ViewChild property so my replacer function comes in action.
JSON.stringify(this, function(key, value) {
const isViewChild = value.nativeElement; // This is for identifying a ViewChild.
if (isViewChild) {
return isViewChild.innerHTML; // I want to resolve it with HTML only.
}
return value;
});
Now, my viewchild looks as follow:
{
...other properties,
myViewChild: '<div>My viewchild translated to html</div>'
}
Other approach is overriding the toJSON function. Using my component again:
#Component({...})
export class MyComponent {
#ViewChild('mySon') myViewChild: ElementRef<any>;
...other properties
toJSON() {
return { myViewChild: this.myViewChild.nativeElement.innerHTML, ...otherProps };
}
}
And when you JSON.stringify the component you are going to get your component with directives resolved.
Try it!
I am working on an Angular application, that listens to the MQTT broker. I am trying to parse the response with JSON.parse() function. It looks simple, but I can't get the correct data to my global variable.
I am publishing this data (id of light and rgb value of that light) :
[{"light":2,"color":{"r":150,"g":24,"b":24}},{"light":3,"color":{"r":150,"g":24,"b":24}},{"light":4,"color":{"r":150,"g":24,"b":24}},{"light":6,"color":{"r":150,"g":24,"b":24}},{"light":7,"color":{"r":150,"g":24,"b":24}}]
The code looks like this.
export class LightsComponent implements OnDestroy {
lightsValues;
private subscription: Subscription;
constructor(private mqttService: MqttService) {
this.subscription = this.mqttService.observe(TOPIC).subscribe((message: IMqttMessage) => {
this.lightsValues = JSON.parse(message.payload.toString());
console.log(JSON.parse(message.payload.toString()));
console.log(this.lightsValues);
});
}
The result of console.log is the weird part, as I am only assigning the same JSON.parse() to the variable as into the console.log -
https://imgur.com/RdDai1B
As you can see, the light parameter is set to 0 for all objects, rgb is set correctly.
However, when I change the lightsValues variable to local like this, I get the correct data.
export class LightsComponent implements OnDestroy {
lightsValues;
private subscription: Subscription;
constructor(private mqttService: MqttService) {
this.subscription = this.mqttService.observe(TOPIC).subscribe((message: IMqttMessage) => {
let lightsValues = JSON.parse(message.payload.toString());
console.log(JSON.parse(message.payload.toString()));
console.log(lightsValues);
});
}
With this code, the data is correct in both console.logs - https://imgur.com/p2xnju9
I need to get this correct data to my global variable, as it serves as an #Input for my other component.
<app-lights-preview [lightsValues]="lightsValues"></app-lights-preview>
The child component :
export class LightsPreviewComponent {
#Input() lightsValues;
.
.
.