-1

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"}}

Example: https://www.typescriptlang.org/play?target=7&ts=4.6.2#code/JYOwLgpgTgZghgYwgAgMrEgYQPbguZAbwFgAoZC5YAEwC5kBnMKUAcwG4zLkQ4BbCPSYsQHLpQYYIOPOHoBZOAAcAPMLYAadaIB8ZAL5kyAGwhhkAKwa5kAXmQApVAHkAcgDolcKAwgAKcQoAckIAIhpQ2lC4ACMEAEYAJgBmUI1Q3gFIjP4UTIg00MksXEhwSLCoOBBqbD547IALYEKqmrrEpogAT1D9fSDA5ABKZDgGNCkZMrAyBFxrU3djbFY-K1x3YulS-DB3VjM-ULba+tDh4fYgA

andresmonc
  • 398
  • 7
  • 21
  • Please provide a [mre] that clearly demonstrates the issue you are facing. Ideally someone could paste the code into a standalone IDE like [The TypeScript Playground (link here!)](https://tsplay.dev/wRRyEw) and immediately get to work solving the problem without first needing to re-create it. – jcalz May 14 '22 at 01:02
  • Never try to translate Java code to TypeScript. It's time for a rewrite. The languages have nothing in common. – Aluan Haddad May 14 '22 at 01:04
  • @AluanHaddad I'm not trying to translate Java to typescript. I'm trying to understand how to handle mapping a typescript interface when the json object contains an object with unpredictable keys – andresmonc May 14 '22 at 01:27
  • You need to learn JavaScript and JSON serialization. JSON is going to handle that as a plane object. It has nothing to do with typescript – Aluan Haddad May 14 '22 at 01:34
  • @AluanHaddad I'm using angular which automatically casts JSON to TypeScript objects ```return this.http.get(this.apiUrl + this.endpoint + id)``` – andresmonc May 14 '22 at 01:37
  • That's not how it works. There is no casting in the language. – Aluan Haddad May 14 '22 at 01:40
  • thank you for this conversation on semantics. appreciate the help – andresmonc May 14 '22 at 01:44
  • JavaScript objects *are* maps with string keys, you don't need `Map` for that. And parsing JSON in JS produces JavaScript objects, so you won't *get* a `Map` if you parse JSON. TypeScript's type system describes JavaScript, it does not change it, so writing `as SiteContent` will not affect the value at runtime; all you're doing there is *claiming* that the value will be of that type. It's a prediction, not a command. – jcalz May 14 '22 at 02:19
  • 2
    In TypeScript you can use a string index signature like `{[k: string]: string}` to represent an object whose keys are unknown but whose values are all `string`s. So [this](https://tsplay.dev/NVZxxN) is how you'd make your example work properly. Does that meet your needs? I could write up an answer explaining all this, but a lot of it would just be explaining how JavaScript works, which is a fairly wide scope. Wondering how to make it more targeted. – jcalz May 14 '22 at 02:22
  • @jcalz thank you so much for your help. I changed the type in the interface to siteContent: {[k: string]: string} and was able to do this.obj.siteContent["randomKey"] to access the key in angular. – andresmonc May 14 '22 at 03:51

1 Answers1

1

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 Maps, 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 strings:

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

Playground link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360
  • Thank you @jcalz I really appreciate your helpfulness and kindness with my lack of understanding of these things. I've worked a lot with Angular/TS but not too much with plain JS. I'm also primarily a Java developer. But again thank you for communicating my misunderstanding in a polite manner – andresmonc May 15 '22 at 20:26