Convert a string to an object and call a function - javascript

I want to store an object as a string, and then convert ot back to an object and call a method of this object.
user.delete() // this works
self.user = JSON.stringify(user)
const storeUser = JSON.parse(self.user)
storeUser.delete() // Error: delete is not a function

If JSON.parse and JSON.stringify would allow a copy of methods, your code would be insecure and would have risks of people running arbitrary code on your server/computer since normally JSON string comes from external sources.
You should allow your User's class to parse a json and create a new instance based on that json:
class User {
constructor(someRandomProperty) {
// ... code
}
static fromJson(json) {
let parsedJson;
try {
parsedJson = JSON.parse(json);
} catch (error) {
throw new Error("Invalid Json User");
}
return new User(parsedJson.someRandomProperty);
}
delete() {
// .... code
}
}
And then you would copy the user like this:
User.fromJson(yourJsonStrinHere)

As has been said in comments, functions/methods are not part of the JSON format so they are not present when you serialize to JSON. Same with class names and such. So, when you call JSON.stringify(), it does not include any information about what type of class it is or any of the methods associated with that object. When you then call JSON.parse(), all you will get back is instance data in a plain object. That's just one of the limitations of JSON.
Normally, you will not serialize code like methods. Instead, you will serialize what type of object it is, serialize the relevant instance data and then you want to reinstantiate the object, you will look at the data to see what type of object it is, call the constructor to make an object of that type and pass to the constructor the data you serialized from the prior object and you will have built a version of the constructor that takes exactly the data you are serializing so it can properly initialize the new object.

Related

Passing a JS object reference to a JSInvokable method in C# without deserializing it

I'm trying to call a C# method from JavaScript, and pass a JS object reference as a parameter without deserializing it. The reason for this is because on the C# side, I only need a reference to the object so I can call functions in the corresponding object.
The way I tried to do this is by simply passing the JS object into invokeMethodAsync as a parameter when calling a C# method, and using the IJSObjectReference type to receive the object. The problem is, that Blazor tries to serialize and deserialize the JS object into JSON when transferring it, which is obviously not what I want.
Here's a simplified version of what I want:
var instance; // this is a DotNetObjectReference to an instance of MyClass
var obj = { ... }; // an arbitrary JS object
await instance.invokeMethodAsync('MyMethod', obj);
class MyClass {
[JSInvokable]
public void MyMethod(IJSObjectReference obj)
{
// pass 'obj' back into JS as parameter,
// or call functions in it by using InvokeAsync/InvokeVoidAsync directly
}
}
And finally, here's the first few lines of the exception:
Uncaught (in promise) Error: System.Text.Json.JsonException: Unexcepted JSON property _events.
at Microsoft.JSInterop.Implementation.JSObjectReferenceJsonWorker.ReadJSObjectReferenceIdentifier(Utf8JsonReader& reader)
at Microsoft.JSInterop.Infrastructure.JSObjectReferenceJsonConverter.Read(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options)
at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
at System.Text.Json.Serialization.JsonConverter`1.ReadCoreAsObject(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
at System.Text.Json.JsonSerializer.ReadCore[TValue](JsonConverter jsonConverter, Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
at System.Text.Json.JsonSerializer.Read[TValue](Utf8JsonReader& reader, JsonTypeInfo jsonTypeInfo)
at System.Text.Json.JsonSerializer.Deserialize(Utf8JsonReader& reader, Type returnType, JsonSerializerOptions options)
To summarize the question: Can I prevent Blazor from trying to serialize a JS object, and pass it as just a IJSObjectReference instead, when calling invokeMethodAsync?

Type 'BaseResponse<IavailableParameters[]>' is missing the following properties from type 'IavailableParameters[]': length, pop, etc

I'm doing a get call to my API and returning a list of an object. I'm then assigning that data to a property in my DataService so I can use it across components.
Here is the code in my component calling the service:
getAvailableParameters() {
this.verificationService.getAvailableParameters(this.itemNumbers).subscribe((res => {
this.dataService.availableParameters = res;
}))}
Here is the call to the API
getAvailableParameters(itemNumbers: string[]){
return this.http.get<BaseResponse<IavailableParameters[]>>(`${this.baseUrl}api/verification/${itemNumbers}`)
}
Here is the property in the DataService that should store the object after getAvailableParameters is called:
export class DataService {
public orderInfo: Iorder;
public availableParameters: IavailableParameters[];
}
As you can see, the object coming back from the API is also wrapped in a BaseResponse object (which I believe to be the problem but don't know how to resolve), Defined here:
import { Error } from './Error'
export interface BaseResponse<T> {
isSuccessful: boolean,
error: Error;
results: T;
}
This is the exact error I'm getting:
ERROR in src/app/Components/verification-reply/verification-reply.component.ts(28,7): error TS2740: Type 'BaseResponse<IavailableParameters[]>' is missing the following properties from type 'IavailableParameters[]': length, pop, push, concat, and 26 more.
When I remove the base response type, it works fine, as I'm assuming it doesn't register that as an array or something. This binding method to the data service object worked fine when the object I returned was not an array.
I am inexperienced in this subject and could use some help figuring out how to resolve this error.
When I remove the base response type, it works fine, as I'm assuming it doesn't register that as an array or something.
That is to be expected because BaseResponse<IavailableParameters[]> is not an an array. It is an object with properties isSuccessful, error, and results. The property results is an array IavailableParameters[].
You need to make sure that whatever type you are providing to this.http.get matches the actual type that is returned from the request. Is it an array of IavailableParameters? Or an object with isSuccessful, error, and results?
If you have a variable response of type BaseResponse<IavailableParameters[]> and you are trying to use it as an array IavailableParameters[], like in the error message that you posted, then just use response.results instead of the entire response object.

Backbone JS collection parse

Hi , I have some issue with parse method!!!!!
as you can see in backbone js document the parse method in collection has this syntax :
collection.parse(response, options)
1) I want to know why we should use / override the parse method and what is the main usage of that?
2) I read some article and I get that the parse method give us the data structure for the client-side.
3 ) I really have issue for understanding the arguments of parse method .
- what is the options for??
can you give me an example of using parse method with two parameters?
Thanks!
The docs have a nice summary:
parse is called by Backbone whenever a collection's models are returned by the server, in fetch. The function is passed the raw response object, and should return the array of model attributes to be added to the collection. The default implementation is a no-op, simply passing through the JSON response.
http://backbonejs.org/#Collection-parse
1) You should return an array of model attributes. If your JSON response only has this then you don't need to do anything. Typically the parse override is used simply to point inside the JSON object at the right part. For example if your response was like this:
{
httpCode: 200,
responseMessage: 'success',
data: [ {model1}, {model2} ...]
}
Then you would need to override parse to point to the data key:
parse: function(response) {
return response.data;
}
2) They meant that the response arg is the object which was returned by the server.
3) The second options arg is the options that was passed to the .fetch call. You don't need to worry about it unless you want to do some specific logic based on the URL, the HTTP method or anything else that can be passed to fetch (and jQuery.ajax options and some Backbone ones like reset).
4)
parse: function(response, options) {
// For some reason POST requests return a different data structure.
if (options.method === 'POST') {
return response.data;
}
return response;
}

Sending a list of custom objects from server to client

I made a Character class shared by the client and server in the same js file.
The server instanciates these characters and stocks them in a characterList object. I send it to the client with socket.emit( 'updated_characters', characterList ), but the client gets back an object of Object, instead of an object of Character, so I can't use my Character's methods on them.
How can I get around that ?
You can't send custom object types directly using socket.io. socket.io uses JSON as the transport format for objects and JSON does not have the ability to record or rebuild custom object types.
So, what you need to do is to create a serialized format for your objects (which will also likely be JSON) and then when you read the data on the other end, you will construct a custom object in your own code and then pass it this data in the constructor from which it will initialize itself.
The canonical ways to do this is to create a serialize method for your custom object that creates a new plain object that contains all the relevant info that needs to be sent over the wire and does not contain references to other objects (since that can't be serialized). Keep in mind that objects often have housekeeping information that does not necessarily need to be sent to the other end. Then, create an initialize method for your custom object that can take this serialized data and properly initialize your object that you can use on the other end.
Here's a simple example:
function MyObject(data) {
if (data) {
this.init(data);
} else {
this.count = 0;
this.greeting = "hello"
}
}
MyObject.prototype = {
init: function(data) {
this.greeting = data.greeting;
this.count = data.count;
},
getData: function() {
return {greeting: this.greeting, count: this.count};
},
talk: function() {
say(this.greeting);
}
};
Then, on the sending end of things, if you have an instance of your object in item, you would send the data with:
socket.emit('updated_characters', item.getData());
And, on the receiving side of things, you would have this:
socket.on('updated_characters', function(data) {
var item = new MyObject(data);
// do something with the item here
});
socket.emit( 'updated_characters', JSON.stringify(characterList) );
we have to do JSON.stringify in order for socket to identify as JSON and display it in front-end

Clean way to specialize (differentiate) the behaviour of Backbone.Model parse method

I have a simple model called BaseModel that extends from a Backbone.Model. According to Backbone.js documentation I've overridden parse method to work with a preexisting API.
The parse method is called in two different situation. When from a collection I want to fetch to grab all data. When from a model I want to fetch a specific data.
Within BaseModel to differentiate the behavior I'm doing the following.
parse : function(response, options) {
var result;
if(options.collection) {
// I'm coming from the fetch that belongs to the collection
// manipulate result here...
} else {
// I'm coming from the fetch that belongs to the model
// manipulate result here...
}
return result;
}
Is this appraoch valid or is there a better way to achieve this?
Edit 1
I thought on Andrew answer but the situation I need to manage is weird. In fact, when the parse method is called the first time (from the collection) data are parsed and properties for the model are created. Then, when the parse method is called from the model itself, additional data are parsed and properties for the model are merged to the first ones.
Edit 2
In my situation, for example, response coming from collection contains an array of objects where each object has a property a. Conversion can be applied, e.g. date obj. Then, response coming from model contains b. Also here conversion can be applied. At the end both properties will be merged into the same model but they are coming from different fetch calls.
Notice that response in collection is already an array. So, I do not to differentiate or split nothing here. I would just know that if I come from collection, I will find a, b otherwise.
Read the fetch in the collection as give me all models, while the other call as based on a model returned from the collection, enrich it by details.
Backbone.Collection also has a parse method. In my opinion the correct way is to implement it for both your BaseModel and your Collection.
The collections parse method only needs convert the data to be an array of unparsed models. It then delegates to the BaseModel parse method automatically to parse each one individually.
e.g.,
BaseModel {
parse : function(response, options) {
var result;
// I'm coming from the fetch that belongs to the model
// manipulate result here...
return result;
}
}
BaseCollection {
parse : function(response, options){
// I'm coming from the fetch that belongs to the collection
// Turn it into an array.
return response.split('mydelim');
}
}
From your edit 2, It looks like your approach is the right idea. I would however say that if I where to do it, I would test the returned object for properties rather than the context of the call so I don't need to care about the datasource,
parse : function(response, options) {
var result = {};
if(response.a){
result.c = response.a;
} else if(response.b){
result.c = response.b;
}
...
return result;
}

Categories