Breezejs automatic GUID parsing - javascript

I am using Breezejs in 'NoDB' mode, meaning I write my metadata by hand. When I create a Breeze query with OData parameters I add a filter by id, say
new breeze.Predicate('iD', datacontext.breeze.FilterQueryOp.Equals, myId)
The var myId is indeed a GUID value (though it's defined as a String), but in my DB and in both my server-side and client-side model it's a string (I can't change the DB structure). the property definition in my metadata model is
dataProperties: {
...
iD: { dataType: DataType.String },
...
}
(I know the property name looks weird, but I have to use this syntax since I have the breeze.NamingConvention.camelCase.setAsDefault() on my datacontext, and the property's name on the DB is ID uppercased)
When I execute the query I see that the corresponding oData filter option in the WebAPI url is like
$filter=ID eq guid'65BEB144-5C0C-4481-AC70-5E61FDAA840D'
which leads me to this server error: No coercion operator is defined between types 'System.Guid' and 'System.String'.
Is there a way to disable this automatic 'parsing' of GUIDs and leave them as strings?
I have temporarily solved this by removing the parsing directly inside breeze's source code so that my webAPI call would look like
$filter=ID eq '65BEB144-5C0C-4481-AC70-5E61FDAA840D'
but I don't like this solution and I would be glad if there was a better one, like parametrize this behaviour in some way. I didn't find anything about this on Breeze's official website.

Breeze uses its metadata to determine that datatype of each property in a query and then uses this information to generate the correct OData filter. So your metadata definition of ID as a string should be correct.
However, in order to perform this operation breeze needs to know the EntityType of your query. For example in the following query
var q = EntityQuery.from("Foo").where(....)
breeze needs to know the EntityType that "Foo" ( a resourceName) corresponds to. Once it has the entity type it can correctly format any filters for specific properties of this entityType. If breeze does not have 'EntityType', then it falls back to guessing about the datatype of each property. In your case, its guessing that the datatype is a 'Guid'
So the fix is to either tell the query directly about the EntityType that you are querying
var q = breeze.EntityQuery.from("Foo).where(....).toType(FoosEntityType);
or you can handle it more globally via via the MetadataStore.setEntityTypeForResourceName method.
breeze.MetadataStore.setEntityTypeForResourceName("Foo", FoosEntityType);
var q = breeze.EntityQuery.from("Foo).where(....); // your original query

Related

Convert a JSON to TW Object of type ANY

Using IBM BPM 8.6
I have a JSON as follows:
tw.local.person = "{\"firstName\":\"Ahmed\",\"job\":\"Doctor\"}";
I am using the BPM helper toolkit to convert the json to TW Object
tw.local.outputObject = BPMJSON.convertJSONToTw(tw.local.person);
RESULTS:
If the outputObject is of type Person (with the attributes firstName and job), it works and the object is created.
If the outputObject is of type any, it doesn't work
How can I get the output in an any object?
Any workaround or a a tweak in the BPM-JSON-Utils.js or json2.js files?
The first thing I would note that in my 8.6 install, calling JSON.parse() just works, so you don't need the community toolkit. That being noted, that approach seems to encounter what is likely the same bug as you are seeing when you try to do it using ANY or Record.
Based on the error it seems that the underlying TWObject won't let you reference member fields that are not explicitly declared. In my tests, using the JSON String -
var json='{ "name" : "Andrew", "value" : "42"}';
I tried -
tw.local.myNvp = JSON.parse(json);
tw.local.myAny = JSON.parse(json);
The first one which was parsing into a variable of type "NameValuePair" from the system data toolkit worked. The 2nd which was trying to parse into an "ANY" failed. I also tried with Record to see if we could get there, but that failed as well.
My suggestion would be to return the raw JSON to the caller and have them invoke the parse line above. I'm assuming the caller is expecting a specific type back, which means the variable isn't an Abstract type, so the parse call should work.
-Andrew Paier

How to create OData V2 entity path dynamically in UI5?

I want to update a table populated with OData service. I am using this approach:
oModel.update("/Products(999)", data, {/*...*/});
I have the selected index stored in a variable (myVar1) and I need to pass that variable to the path string. The problem is Products(999) - this is working with the hard coded row but how to replace 999 with a variable?
Create the path dynamically via the API createKey from the V2 ODataModel:
const path = myODataV2Model.createKey("/Products", {
// Key(s) and value(s) of that entity set
"ProductID": myVar1, // with the value 999 for example
"AnotherKeyProperty": "...",
});
myODataV2Model.update(path/*, ...*/);
Compared to concatenating strings for the path manually, createKey offers the following advantages:
It outputs the key value always in the correct format corresponding to the EDM type of the given property (using ODataUtils.formatValue internally). E.g.: If ProductID has the type Edm.Int64, UI5 appends the character "l" in the output string aligning with the OData specification: "999" → "999l"
It makes sure that all keys are encoded according to the URI standard (using encodeURIComponentapi internally). E.g.: ProductID='sp ace' → ProductID='sp%20ace'
It outputs the key values always in the correct order no matter which backend system serves the metadata. Given the same metadata definition, it is possible that one system serves the metadata with keys in different orders than others. In such cases, if keys are just concatenated manually, the app would fail horribly throwing vague errors when it's transported to the system serving different key orders.
Note
Since createKey relies on the information from the service metadata, the API should be performed after $metadata is loaded. For this, the promise based API metadataLoaded can be used.
myODataV2Model.metadataLoaded().then(/*createKey*/);
use the javascript concatenation operator + to merge the value of the variable into the url string:
var sIndex = "123";
oModel.update("/Products(" + sIndex + ")", oData, {success: mySuccessHandler, error: myErrorHandler});
by the way: numeric types convert hereby automatically into strings.

How to remove a property from document - mongo / loopback

I have an old property on one of my models, and I'd like to remove it from all the documents in a collection. I've tried posting via /upsertWithWhere using the id to update by:
passing in undefined for the value which results in "http error 400 bad request"
passing in null which just sets the property to null
I also was thinking I could do a regular POST and just overwrite each document, but these particular documents are large and I'd rather not do that.
Is there a way to simply patch it?
Edit: Need an answer that implements this Via the Loopback API.
This query should do the trick:
db.collection('collection_name').update({},{$unset: {"old_property": ""}}, {multi:true})
Obviously, just make sure you insert into "old_property" the field name of that old property.
Explaining the query a little further...
"{}" matches all documents in the collection
"{$unset: {"old_property": ""}" removes the field(s) specified
"{multi:true}" (An optional field for update) Allows you to update multiple documents when set to true
Used this as a reference: https://docs.mongodb.com/manual/reference/method/db.collection.update/#multi-parameter

NewtonSoft JSON converter serializes but does it weirdly. How to fix?

Given the following object
Friend Class GetLocationsResult
Public Property X1 As String
Public Property X2 As String
Public Property X3 As String
Public Property X4 As String
Public Property X5 As String
Public Property X6 As Double
Public Property X7 As Double
End Class
And it is declared and instantiated thusly:
Dim objList as List(of GetLocationsResults) = new List(of GetLocationsResults)
And objList is populated via an iterator that churns through a collection of objects/aggregate classes. The iterator just shoves values into a new GetLocationsResult object and then adds it to the list.
And given the NewtonSoft JSONConvert.SerializeObject(objList) results:
{"d":"[{\"X1\":\"Store Name\",\"X2\":\"Address\",\"X3\":\"City\",\"X4\":\"State\",\"X5\":\"Zip\",\"X6\":Coord1,\"X7\":Coord2}]"}
This point has been addressed and is no longer an issue
There are several issues with this result set. First, for whatever odd
reason, the object being named "d" is not acceptable.
How can I specify something other than "d" for the "name" of the json array?
When I attempt to JSON.parse the response, it needs to be in the following format in order to actually get to the data:
resultSet = JSON.parse(data.d);
console.warn(resultSet[0].X1);
Having to say resultSet[0] is, of course, not acceptable.
How do I cause the 'JSONConvert.Serialize' method to not wrap the response in such a way that I have to refer to the first index of the
resulting JSON data so that I can say resultSet.X1 instead of
resultSet[0].X1?
As requested, here is some more detailed information that may be relevant to the issue at hand.
The data is being returned from a WCF service. The exposed method is decorated thusly:
<WebInvoke(Method:="GET", ResponseFormat:=WebMessageFormat.Json)>
and returns type String. The service is being consumed by a desktop application and a mobile platform. The desktop website can have its own method and, in fact, does because we don't want to deal with X1, X2, etc while the mobile platform devs have declared that as necessary. As noted, the method returns a number of results in the form of a custom, aggregate class which is then shoved into a class object which is only a collection of properties. The return statement is thus:
Return JsonConvert.SerializeObject(retVal)
where retVal is a List(of GetLocationsResult)
So while having to access the data via index may be fine for the website, it is not acceptable for the mobile platform. Because each will run their own methods, it is possible to have a unique solution for both if needed.
Three things.
It sounds like you're returning that back through an ASP.NET ASMX
ScriptService or ASPX [WebMethod]. When using one of those
endpoints, you don't need to (and shouldn't) manually serialize the
object into JSON. ASP.NET will do that for you automatically. You don't need to use JSONConvert at all; just return your object as the result. That's why you're seeing all that escaped data after the .d. It's JSON serialized twice.
As WhiteHat mentioned, the .d is a good thing in some situations. It's introduced by default in these ASP.NET JSON services and can't easily be removed on the server-side, but is easy to account for when you parse the response on the client-side.
You are returning a List(of GetLocationResults), which maps to a JavaScript array. That's why you need to use resultSet[0].X1 to access a value in the result. If you want to access that value via resultSet.X1, you need to be sure to return only a single GetLocationResults object instead of a List of them.
Does that help? If you could update your question with a more complete example of your server-side and client-side code, I could give you an example of how to address the preceding three issues.

How can I get the key as well as the value when using db.js to query IndexedDB?

I have an IndexedDB of changes. I add an item like this, and then log the result to check the key has been created successfully:
_this._idb.add('steps', step).done(function (items) {
var item = items[0];
_logger.log("ADDED STEP", { id: item.__id__, step: item }, "CT");
});
The output from this is as expected:
...as you can see, the id has been added to the object when it is stored.
However, when I query the db to getback a list of objects, using this code:
this._idb.steps.query('timestamp').bound(start, end).execute().done(function (results) {
_logger.log("Results", results, "CT");
}
I don't get the id as part of the object that is returned:
... and the lack of id makes updating and deleting impossible.
How can I get the id of the item when I query indexed db using db.js - or am I approaching this in the wrong way, and is there something else I should be doing?
(Note: I'm using TypeScript to compile the JS, but I don't think that's especially relevant to this question)
This is expected behaviour, you're only going to get the __id__ property if you don't define a keyPath in your db schema.
Because there's no keyPath defined the value is not associated with it in indexeddb, it's only added to the resulting object after it has been added, because at that point in time we know the auto-incremented value that IndexedDB has assigned to it.
Since the value isn't really part of the object I don't have any way to assign it to the object when it comes out during a query, maybe I could use the position in the array but that's more likely to be wrong than right.
If you want the ID to be persisted against the object then you need to define a keyPath as part of the object store schema and the property will be added to the resulting object and available and it will be on the object returned from a query.
Disclaimer - I wrote db.js
Looking at the source, __id__ is only defined when your keyPath is null in the add() method. From what I'm seeing, you'll never see this in a query() response.
In IDB null keyPaths are allowed only when using auto-incrementing ("out-of-line") keys. So if you're getting the object back, it should have an auto-incrementing key on it or some other keyPath.
The __ prefix in JavaScript usually means the developer intended it to be a "private" property. I'm guessing this is for internal use and you shouldn't be counting on this in your application code.
Consider using explicit, so-called "in-line" keys on your object store.
The goal of db.js is easy and simple to use. Your is advanced use case.

Categories

Resources