I am working on a web application in Angular and am making the following call to a service
return this.http.get<SiteContent>(this.apiUrl this.endpoint id)
The issue is that the JSON which is being mapped to a SiteContent interface contains an Object within one of that fields where the content is unpredictable. How can I handle this in my code to be able to access those fields in the siteContent
field? ideally I'm looking to be able to do something like this
return this.http.get<SiteContent>(this.apiUrl this.endpoint id).subscribe((obj) => {
console.log(obj.siteContent.get("randomKey"));
})
The above example results in the following error message when I try to do a get() by key.
ERROR TypeError: obj.siteContent.get is not a function
SiteContent interface
export interface SiteContent {
id: string;
name: string;
siteContent: Map<string,string>
}
Sample JSON
{"id":"abc123","name":"name name","siteContent":{"random1":"hi","random2":"hey"}}
CodePudding user response:
JavaScript background, not specific to TypeScript:
JSON is a data format based on JavaScript object literal notation. When you use the JSON.parse()
method to deserialize a JSON string into a JavaScript value, you will get the same sort of objects you get with JavaScript object literals:
let foo = { "a": 1, "b": "two", "c": false };
console.log(foo) // { "a": 1, "b": "two", "c": false }
let bar = JSON.parse('{ "a": 1, "b": "two", "c": false }');
console.log(bar) // { "a": 1, "b": "two", "c": false }
Objects in JavaScript are key-value pairs where the keys are (generally) strings but the values can be of any type. These keys do not need to be declared ahead of time, as far as JavaScript is concerned. All objects in JavaScript are "expando" objects.
You access a property of an object either by using dot notation (if the key name is a known valid identifier):
foo.a;
or by using bracket notation (customarily only if the key name is not a valid identifier or if it is the result of some expression):
foo["a"];
const k = "a";
foo[k];
You don't use get()
to access properties on such objects. There is a JS Map
class which stores key-value pairs and has a get()
method to retrieve such pairs. But a Map
is not the same as a plain JS object. There are reasons to use Map
s, but processing deserialized JSON is not generally among them.
At runtime you should therefore have code like this:
let json = JSON.parse(
'{"id":"abc123","name":"name name","siteContent":{"random1":"hi","random2":"hey"}}'
);
console.log(json.siteContent.random1); // hi
TypeScript adds a static type system to describe JavaScript. It generally doesn't add any functionality or change the way JavaScript works. An interface
type like SiteContent
is a way of describing the shape of certain JavaScript objects you expect to interact with at runtime.
Object types in TypeScript can have known properties with specific string literal keys, like id
or name
. However, it is also common to use JavaScript objects as expando objects where the properties have a known value type, but the keys aren't known ahead of time. And so TypeScript allows you to give an object type an index signature. The object type {[k: string]: number}
means "you may index into this object with any key k
of type string
, and if there is a property there, it will be of type number
. In your case, you want the siteContent
property to be an object type whose keys are arbitrary but whose values are string
s:
interface SiteContent {
id: string;
name: string;
siteContent: { [k: string]: string }
}
Armed with that index signature, your code will now compile in TypeScript with no errors:
let json = JSON.parse(
'{"id":"abc123","name":"name name","siteContent":{"random1":"hi","random2":"hey"}}'
) as SiteContent;
console.log(json.siteContent.random1); // hi