deep access in plain objects with Immutable - immutable.js

Consider the following example:
const stickers =
new OrderedMap().set(1, {hero: "batman", name: "Bruce"});
stickers.getIn([1]); //=> { hero: 'batman', name: 'Bruce' }
stickers.getIn([1, "hero"]); //=> undefined
Why is the result of that second getIn undefined?
The docs on ImmutableJS state:
Plain JavaScript Object or Arrays may be nested within an Immutable.js Collection, and getIn() can access those values as well
Therefore, it follows that it makes no difference if value of the OrderedMap is a plain javascript object or an Immutable collection - however we can see that the bug goes away if we convert that plain object to an Immutable Collection first:
const stickers =
new OrderedMap().set(1, fromJS({hero: "batman", name: "Bruce"}));
stickers.getIn([1]); //=> { hero: 'batman', name: 'Bruce' }
stickers.getIn([1, "hero"]); //=> 'batman'

I believe this is because you're looking at the docs for a newer version of the library than you're using. I just tried out your code with version 4.0.0-rc.9 and it works as expected.
For example:
const stickers =
Immutable.OrderedMap().set(1, {
hero: "batman",
name: "Bruce"
});
console.log(stickers.getIn([1]));
console.log(stickers.getIn([1, "hero"])); //
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/4.0.0-rc.9/immutable.js"></script>

Related

Trying to get some position of JSON using Observables

Sorry for the question, but I'm newer in Typescript and Ionic and Im a bit confused about how should I proceed.
I have a JSON file with 150 entries based on an interface I'm declared quite simple:
export interface ReverseWords {
id: number;
todo: string;
solution: string;}
On the other hand, I have a service which reads the json file and returns an Observable of this type (ReverseWords)
getReverseWords() {
return this.http.get<ReverseWords>('/assets/data/reves.json');}
On .ts file, I call the service and I have all the content of the JSON file. What I want to do (and Im not able to do) is get only one entry based on a random position.
On .ts file:
reverseWords: Observable<ReverseWords>; // All the JSON content
reverseWordsSelected: Observable<ReverseWords>; // I would like to get one entry here
On ngOnInit():
this.reverseWords = this.dataservice.getReverseWords();
Everything is fine until here, I've got all the content and I can log it in console. I'm using Observables so I need to subscribe to it to get the information. And I use rxjs/operators pipe and filter to try it, but nothing is showing in the chrome developer console (not even an error).
const reverseWordSelectedByPosition = this.reverseWords.pipe(filter(reverseWord => reverseWord.id === randomPosition));
reverseWordSelectedByPosition.subscribe(console.log);
Could anybody help me and tell me what I'm doing wrong?
Other thing I've tested is to do the following in the service:
getReverseWords() {
return this.http.get<ReverseWords[]>('/assets/data/reves.json');}
And then in the .ts file:
reverseWords: Observable<ReverseWords[]>;
But I have the same problem.
Finally, the most weird thing is that if I write in the .ts file this simple test:
const test = from([
{
id: 1,
todo: 'chapter',
solution: 'r-e-t-p-a-h-c'
},
{
id: 2,
todo: 'claustrofobia',
solution: 'a-i-b-o-f-o-r-t-s-u-a-l-c'
},
{
id: 3,
todo: 'keyboard',
solution: 'd-r-a-o-b-y-e-k'
}
]);
Everything is fine and I can see on the log only 1 entry if I choose 2, for example.
Any help or advice??
Thanks and sorry for the long approach!!
As TotallyNewb suggested, I show an example of the JSON file, with only 3 entries:
[
{
"id": 1,
"todo": "chapter",
"solution": "r-e-t-p-a-h-c"
},
{
"id": 2,
"todo": "claustrofobia",
"solution": "a-i-b-o-f-o-r-t-s-u-a-l-c"
},
{
"id": 3,
"todo": "keyboard",
"solution": "d-r-a-o-b-y-e-k"
}
]
Since you are getting the whole array you can use map
this.reverseWords.pipe(
map((items) => items[Math.floor(Math.random() * items.length)]), // Creates a random index based on the array length
)
.subscribe(console.info);
If you want to pass the result to reverseWordsSelected, you can change it to a Subject and pass the value to it from the subscription of reverseWords with .next().
You can check out this stackblitz for a working example

Populate array in react-redux-firebase

react-redux-firebase supports replacing ids with the corresponding value in a lookup object – like a join – through a config object called populates (and later using a convenient mapping function inside mapStateToProps).
Unfortunately, the documentation of react-redux-firebase is rather rudimentary in this area (and in others)...
Can someone tell me, whether it's possible to populate a list of ids?
For example:
// from:
{ friends: [1, 27, 42] }
// to:
{ friends: [
{ id: 1, name: 'Charlie' },
{ id: 27, name: 'Parker' },
{ id: 42, name: 'Brown' },
] }
Testing version 3.0.0, it seems that populating a list works exactly the same as replacing a single field. Instead of receiving back an object you will receive an array of replaced objects.

Can one add a complex type item to ListModel?

I would like to have a ListModel-like structure to display inputs of a simple state machine. Each input might consist of several strings/ints. So I need each item of the ListModel to be able to store a list of data (strings with names of the input's parameters, or dictionaries with strings etc). At the moment I cannot append an item with a list property to a ListModel.
So the ListModel looks like this:
ListView {
anchors.fill: parent
model: ListModel {
id: listModel
}
delegate: Text {
text: inputs[0]['name']
}
}
And when the state changes I want to update the model and append elements like this:
var state = {
name: "abcd",
inputs: [{name: 'a'}, {name: 'b'}, {name: 'c'}]
}
listModel.append(state);
Current version of the code returns error TypeError: Cannot read property 'name' of undefined. It does not see the list.
According to this question there might be issues with using lists in the items of ListModel. But it seems irrelevant to my case. Maybe I need to use lists and dicts differently in QML, maybe I had to write text: inputs[0].name in delegate (which I tried) or something else (suggestions?).
Could someone suggest how to make a more or less complex item (basically, it is standard JSON) in a ListModel? It is not clear, since documentation and blogs/questions deal with strings all the time. Is there some helpful documentation which I missed? What are good practices to do it in QML? Should I use some custom objects?
List data can be added to a ListElement, according to the documentation and as you correctly did in your imperative code. However, nested roles are not really arrays. They are ListModels themselves. That's because, by design, QML does not produce a notification if an element of an array changes, which would be a show-stopper in a model-view-delegate setting.
Since the nested role is a model, you can use model's functions. For instance, this example works fine:
import QtQuick 2.5
import QtQuick.Window 2.2
Window {
id: window
width: 600
height: 400
visible: true
ListView {
anchors.fill: parent
model: ListModel {
id: listModel
}
delegate: Text {
text: name + inputs.get(index % inputs.count).name // accessing the inner model
}
}
MouseArea {
anchors.fill: parent
onClicked: {
var state = {
name: "abcd",
inputs: [{name: 'a'}, {name: 'b'}, {name: 'c'}]
}
listModel.append(state);
}
}
}
According to your question, the input is plain JSON. In that case, consider the usage of JSONListModel in place of ListModel. It exposes a set of API which matches XMLListModel, via JSONPath, and could possibly represent the perfect solution for your scenario.

Mongoose.js getter that inflates subdocuments on Find()?

Is there an inverse of Mongoose.js validation that can inflate the subdocument when the parent is retrieved? I may have been looking at the docs so long I'm not recognizing an existing feature for what it is.
A beauty of MongoDB is that the query specifications (e.g. {likes: {$gt: 10, $le: 14}} are themselves Javascript objects, and until recently have been storing them in a MongoDB instance as subdocuments.
However, upgrading from MongoDB 2.4 to 2.6, these are no longer valid to store as such, and am now getting the error: The dollar ($) prefixed field '$or' ... is not valid for storage
Am thus in the situation in this Google Groups Discussion. The author there suggests flattening the document to a String. This situation can also occur if the subdocuments have legitimate Javascript attributes that have embedded dots (e.g. {"802.11g": ...})
That's easy enough to by specifying JSON.parse and JSON.stringify as the getter/setter in Mongoose.js:
var ProjectSchema = new Schema({
name: { type: String, required: false, default: "New project" },
spec: {type: mongoose.Schema.Types.Mixed, set: JSON.stringify, get: JSON.parse},
});
But the getter only gets called if I explicitly ask for the attribute value. The attribute is still a string underneath and gets passed as such:
Project.findById(req.params.projectId, function(err, project) {
console.log("......"+(typeof project.spec)) // project.spec is an object!
res.send(project); // project.spec is a String!
});
Obviously i can call model.spec = JSON.parse(model.spec) within each Model.find(...) call and for each flattened attribute but it'd be nice to do it at one central location.
https://groups.google.com/forum/?fromgroups=#!topic/mongoose-orm/8AV6aoJzdiQ
You can invoke your getter in res.send by adding the {toJSON: {getters: true}} option to the ProjectSchema definition. You'll probably want to enable that for the toObject option as well for cases like passing the doc to console.log.
var ProjectSchema = new Schema({
name: { type: String, required: false, default: "New project" },
spec: {type: mongoose.Schema.Types.Mixed, set: JSON.stringify, get: JSON.parse},
}, {
toJSON: {getters: true},
toObject: {getters: true}
});
Docs here.

Extjs store reader fully qualified json property names

Using Extjs 3+ and server side is sending back the following JSON:
{"com.klistret.cmdb.ci.pojo.QueryResponse": {"com.klistret.cmdb.ci.pojo.successful":true,"com.klistret.cmdb.ci.pojo.count":1,"com.klistret.cmdb.ci.pojo.elements":{"com.klistret.cmdb.ci.pojo.id":123,"com.klistret.cmdb.ci.pojo.name":"Mars","com.klistret.cmdb.ci.pojo.fromTimeStamp":"2010-07-08T16:38:00.478+02:00","com.klistret.cmdb.ci.pojo.createTimeStamp":"2010-07-08T16:38:00.478+02:00","com.klistret.cmdb.ci.pojo.updateTimeStamp":"2010-10-25T15:02:09.446+02:00","com.klistret.cmdb.ci.pojo.type":{"com.klistret.cmdb.ci.pojo.id":1,"com.klistret.cmdb.ci.pojo.name":"{http:\/\/www.klistret.com\/cmdb\/ci\/element\/logical\/collection}Environment","com.klistret.cmdb.ci.pojo.fromTimeStamp":"2009-08-05T11:20:12.471+02:00","com.klistret.cmdb.ci.pojo.createTimeStamp":"2009-08-05T11:20:12.471+02:00","com.klistret.cmdb.ci.pojo.updateTimeStamp":"2009-08-05T11:20:12.471+02:00"},"com.klistret.cmdb.ci.pojo.configuration":{"#www.w3.org.2001.XMLSchema-instance.type":"com.klistret.cmdb.ci.element.logical.collection:Environment","#Watermark":"past","com.klistret.cmdb.ci.commons.Name":"Mars"}}}}
The reader is setup up as follows:
var reader = new CMDB.JsonReader(
{
totalProperty : 'com.klistret.cmdb.ci.pojo.count',
successProperty : 'com.klistret.cmdb.ci.pojo.successful',
idProperty : 'com.klistret.cmdb.ci.pojo.id',
root : 'com.klistret.cmdb.ci.pojo.elements'
},
[
{name: 'Id', mapping: 'com.klistret.cmdb.ci.pojo.id'},
{name: 'Name', mapping: 'com.klistret.cmdb.ci.pojo.name'}
]
);
The store as:
var ds = new Ext.data.Store({
proxy : new Ext.data.ScriptTagProxy({
url : 'http://sadbmatrix2:55167/CMDB/resteasy/element'
}),
reader : reader
});
The reader extends the Ext.data.JsonReader as explained by http://erichauser.net/2007/11/07/more-wcf-json-and-extjs/ to remove the "com.klistret.cmdb.ci.pojo.QueryResponse" start node in the JSON returned from the server.
The extended reader never gets called. Assuming the problem is due to has fully qualified property names in the JSON object returned (ie. "com.klistret.cmdb.ci.pojo.name" rather than just "name").
Anybody use gotten around this?
We worked it out (well, mostly Matthew did) in the comments:
ScriptTagProxy needs the server to wrap the JSON data in a function call so that your local code can get access to it.
Instead of the server emitting something like:
{here:'is data}
it needs to return
somefunc("{here:'is data'}");
That way, your client-side implementaiton of somefunc() is called and can process the returned data.