Related
I'm trying to represent a JSON structure in swift for use with EVReflection. The string looks like this:
{
"date": 2457389.3333330001,
"results":
{
"sun": [[559285.95145709824, 202871.33591198301, 61656.198554897906], [127.6163120820332, 948.44727756795123, 406.68471093096883]],
... etc ...
"geomoon": [[-401458.60657087743, -43744.769596474769, -11058.709613333322], [8433.3114508170656, -78837.790870237863, -26279.67592282737]]
},
"unit": "km"
}
I've tried several approaches to model the inner "results" dictionary, which is keyed with a string and always has values that are an array of two elements, each of which have three doubles (i.e., [String: [[Double]]] is one model I've tried). No matter what I do, I get an error along these lines:
ERROR: valueForAny unkown type , type _NativeDictionaryStorage<String, Array<Array<Double>>>. Could not happen unless there will be a new type in Swift.
Does anyone have an idea how to model this so EVReflection is happy? I've also had the sense from the phrase Could not happen unless there will be a new type in Swift that there's another method or different modeling with explicit subtypes I can add that would give it what it needs, but I can't puzzle out what to do from the docs.
In Swift a Double is a struct. Doing reflections with structs have some limitations. I prefer using NSNumber instead.
My first thought would be an object model like this:
class MyObject: EVObject {
var date: NSNumber?
var results: Result?
var unit: String?
}
class Result: EVObject {
var sun: [[NSNumber]]?
var geomoon: [[NSNumber]]?
}
But your object has a nested value. Until now I never have tested EVReflection with a structure like this. I will try adding a unit test for this in a moment. It would have been easier if the inner array was an object (something like an object with an x, y and z value?)
Update:
This seems to work. I tested this with the following unitTest:
func testNestedArry() {
let json = "{\"date\": 2457389.3333330001,\"results\": { \"sun\": [[559285.95145709824, 202871.33591198301, 61656.198554897906], [127.6163120820332, 948.44727756795123, 406.68471093096883]], \"geomoon\": [[-401458.60657087743, -43744.769596474769, -11058.709613333322], [8433.3114508170656, -78837.790870237863, -26279.67592282737]] }, \"unit\": \"km\" }"
let myObject = MyObject(json: json)
print(myObject.toJsonString())
}
And the output of myObject was:
{
"date" : 2457389.333333,
"results" : {
"sun" : [
[
559285.9514570982,
202871.335911983,
61656.19855489791
],
[
127.6163120820332,
948.4472775679512,
406.6847109309688
]
],
"geomoon" : [
[
-401458.6065708774,
-43744.76959647477,
-11058.70961333332
],
[
8433.311450817066,
-78837.79087023786,
-26279.67592282737
]
]
},
"unit" : "km"
}
Update 2:
As discussed below the planet 'keys' are not a fixed set. So actually this should be handled as a dictionary. To do something like this you can use a 'hack' by using the setValue forKey to populate a dictionary. For doing this you would need a data model like this:
class MyObject: EVObject {
var date: NSNumber?
var results: Result?
var unit: String?
}
class Result: EVObject {
var planets = [String: [[NSNumber]]]()
// This way we can solve that the JSON has arbitrary keys
internal override func setValue(value: AnyObject!, forUndefinedKey key: String) {
if let a = value as? [[NSNumber]] {
planets[key] = a
return
}
NSLog("---> setValue for key '\(key)' should be handled.")
}
}
You do have to be aware that if you want to create json for this, that you will have an extra level in your hierarchy for planets. If you don't want this, then there is a workaround possible by changing the results property to String: [[NSNumber]] You would then also need to implement the EVCustomDictionaryConverter. See the workaround unit tests to see how that would work.
I am new to Angular, so apologies in advance if this seems an obvious question...
I know how to display a simple array of objects using the ng-repeat directive, but I'm not sure how to structure more complex layers of information.
For example: If I wanted to list Premier League football clubs, I could simply create an array of objects, the array listing clubs, with each club being an objects containing key-value pairs on various pieces of information or data relating to that club:
$scope.clubs = [
{
name: "Arsenal",
nickname: "Gunners",
clubBadge: "arsenal.jpg",
founded: "1886"
},
{
name: "Newcastle United",
nickname: "Magpies",
clubBadge: "newcastle.jpg",
founded: "1892"
}
// etc...
]
That's fine. But then what I might want to list the players within each club. For example:
// the following being the team of Newcastle United...
{
GK: "Rob Elliot",
LB: "Paul Dummett",
CB: "Fabricio Coloccini",
CB: "Chancel Mbemba",
RB: "Daryl Janmaat",
LW: "Andros Townsend",
MF: "Georginio Wijnaldum",
MF: "Jack Colback",
RW: "Moussa Sissoko",
ST: "Ayoze Perez",
ST: "Aleksander Mitrovic",
}; // and so on for other clubs...
How would I attach the above object to Newcastle Utd (and likewise for other clubs) in the original array, given that it is only a random index within an unordered array? What would be correct way of structuring this information holistically?
I could take this even further by providing stats on each individual player, such as:
{ // stats for Moussa Sissoko
speed: "86",
ballControl: "71",
strength: "85",
vision: "79"
}
{ // stats for Ayoze Perez
speed: "78",
ballControl: "83",
strength: "69",
vision: "78"
}
Again, I have listed these as individual objects. But I don't know what array to link them to their respective clubs, or how to connect each array (assuming there were three separate arrays: $scope.clubs, $scope.players, $scope.attributes).
If Newcastle Utd is the 10th club in the array, it would be $scope.clubs[9], but I don't want to have to create an entirely new array for the players that has no link to the $scope.clubs[] array. Ideally I want it all to be connected.
Is ng-repeat sufficient to access the model data in these cases or would a more sophisticated directive be required? I'm looking to build the information so it is easy to update and display the data in the view.
Sorry this is a little long-winded - if I knew how to phrase my problem more succinctly, I would!
Any advice here would be massively appreciated. Thanks in advance.
Var clubs = [
{
Name : "clubname",
Players: [
{
Name: "name"
},
{
Name:"othername"
}
];
}
];
enter code here
You can put objects inside objects, with you example will look like this:
$scope.clubs = [
{
name: "Arsenal",
nickname: "Gunners",
clubBadge: "arsenal.jpg",
founded: "1886",
players: [
{
//all the player stuff
},
{
//Other player stuff
}
//Continue with all the players
]
},
{
name: "Newcastle United",
nickname: "Magpies",
clubBadge: "newcastle.jpg",
founded: "1892",
players: [
{
//all the player stuff
},
{
//Other player stuff
}
//Continue with all the players
]
}
// etc...
]
And you can access like this $scope.clubs[0].players, I recommend you to give the clubs an Id, that way is going to be much easier to play with the data. To do this, if you have access to the source you can add it from there, if not, you could use foreach loop on the object and add the Id property
Given is a nested model structure like this:
Model Website
+ id
+ name
+ images[] // List of Image instances
Model Image
+ imageName
+ imageUrl
A serialised version of the response looks like:
{
"id": 4711,
"name": "Some name",
"images" [
{"imageName": "Beach", "imageUrl": "http://example.com/whatever.jpg"},
...
]
}
This nested model set is persisted in a document store and is returned on request by Website.id.
There is no by-id-relation to the nested list of images, as they are persisted as a list directly in the parent model. As far as I know, the classic relations in Ext.data.Model refer to the related models via a by-id-relation.
The question is: Is there any way that I can tell the parent model to use the Image model for each of the children in it's images list?
As a first step, you can make your images data to be loaded into the model by using a field type of auto:
Ext.define('My.Model', {
extend: 'Ext.data.Model'
,fields: [
{name: 'images', type: 'auto'}
// ... other fields
}
});
Then:
myModel.get('images');
Should return:
[
{"imageName": "Beach", "imageUrl": "http://example.com/whatever.jpg"},
...
]
From there, you should theoretically be able to implement a fully automatized solution to creates the models from this data, and -- the hardest part -- try to keep these created records and the children data in the parent model synchronized. But this is a very involved hack, and a lot of entry points in Ext code base have to be covered. As an illustration, I once tried to do that for "has one" relations, and that represent a lot of code. As a result, I never took the time to consolidate this code, and finally never used it.
I would rather advocate for a simple and local (to the model) solution. You can add a simple method to your model to get the images as records. For example:
Ext.define('My.Model', {
// ...
,getImages: function() {
var store = this.imageStore;
if (!store) {
store = new Ext.data.Store({
model: 'My.ImageModel'
,data: this.get('images') || []
});
this.imageStore = store;
}
return store;
}
});
Creating a store for the associated model will save you from having to play with the proxy and the reader. It also gives you an interface that is close to Ext's default one for associations.
If you need support for loading images more than once for the same parent record, you can hook on the field's convert method.
Finally, you may also need to handle client-side modifications of associated data, in order to be able to save them to the server. If your associated model allows it, you could simply use the children store's sync method (and don't forget to update the parent model's data in the sync callback!). But if your associated model isn't connected to an endpoint on the server-side, you should be able to hook on the serialize method to save the data in the associated store (as opposed to the one stored in the parent record, that won't get updated if you work with the associated store).
Here's a last example showing both:
Ext.define('My.Model', {
extend: 'Ext.data.Model'
,fields: [
{
name: 'images'
,type: 'auto'
// enables associated data update
,convert: function(data) {
var store = this.imageStore;
if (store) {
store.loadData(data || []);
}
return data;
}
// enables saving data from the associated store
,serialize: function(value, record) {
var store = record.imageStore,
if (store) {
// care, the proxy we want is the associated model's one
var writer = store.proxy && store.proxy.writer;
if (writer) {
return Ext.Array.map(store.getRange(), function(record) {
return writer.getRecordData(record);
});
} else {
// gross implementation, simply use the records data object
return Ext.pluck(store.getRange(), 'data');
}
} else {
return record.get('images');
}
}
}
// ... other fields
}
,getImages: function() {
var store = this.imageStore;
if (!store) {
store = new Ext.data.Store({
model: 'My.ImageModel'
,data: this.get('images') || []
});
this.imageStore = store;
}
return store;
}
});
Please notice that I haven't tested this code, so it might still contains some mistakes... But I hope it will be enough to give you the general idea!
I'm just starting with Backbone.js and I am having trouble with nested models and collections.
For this example, I only have a single endpoint, /vocabulary.json.
Here is a sample of what that will return:
[
{
"id": 1,
"words": [
{
"native": "hello",
"foreign": "hola"
},
{
"native": "yes",
"foreign": "si"
},
{
//... More words in this lesson
}
]
},
{
//... More lessons coming from this endpoint
}
]
It's basically of collection of lessons, and each lesson has a collection of vocabulary words.
How could I create a words collection without another url endpoint (required by collections, it seems)?
Here's what I have so far. Actually, this is a stripped down, basic version because everything I'm trying isn't working.
/entities/vocabulary.js
Entities.Vocabulary = Backbone.Model.extend({});
Entities.Vocabularies = Backbone.Collection.extend({
model: Entities.Vocabulary,
url: "/vocabulary.json"
});
// Here is where I am struggling
Entities.Vocabulary.Word = Backbone.Model.extend({
// what to do?
});
Entities.Vocabulary.Words = Backbone.Collection.extend({
// what to do?
// Need some method to go into the Entities.Vocabularies Collection, pluck a given id
// and return the "words" attribute as a new Collection to work from.
});
Perhaps, I am thinking about this completely wrong, but I am hoping I have explained my problem well enough to help you help me.
you are almost there. You can use parse method on the model where you can write up your logic of associating the words collection to the vocabulary model.. Something in these lines.
// This would be your main Model
// Set the idAttribute on it
// Use the parse method here which hits before initialize
// where you attach the words collection on each Vocabulary Model
Entities.Vocabulary = Backbone.Model.extend({
idAttribute : 'id',
parse: function (response) {
// If theresponse has wods in response
// attach it words collection to the Vocabulary Model
if (response.words) {
this.words = new Entities.Vocabulary.Words(response.words || null, {
parse: true
});
}
// Delete the words object from response as the collection is already
// created on the model
delete response.words;
return response;
}
});
// Collection of Vocabulary
Entities.Vocabularies = Backbone.Collection.extend({
model: Entities.Vocabulary,
url: "/vocabulary.json"
});
// This would be the model for Word inside a Specific Vocabulory
// Associate a idAttribute if it has one.
// Add a parse method if you have any other extra processing for this model
Entities.Vocabulary.Word = Backbone.Model.extend({
});
// Just a Collection of words for the vocabulory
Entities.Vocabulary.Words = Backbone.Collection.extend({
});
// Pass the object, and pass in the parse: true
// parameter so that parse is called before initialize
var vocabularies = new Entities.Vocabularies(navi, {
parse: true
});
// If you want to fetch a new collection again you would just do
//vocabularies.fetch({parse: true});
console.log(mainCollection);
So each model should have a words collection directly on the Vocabulary model.
Check Fiddle
I have an API that is producing GeoJSON data of a number of Venues and Events that are occurring at each Venue.
See an example output:
{
"crs":null,
"type":"FeatureCollection",
"features":[
{
"geometry":{
"type":"Point",
"coordinates":[
-122.330056,
47.603828
]
},
"type":"Feature",
"id":39,
"properties":{
"city_slug":"seattle",
"neighborhood_name":"Downtown",
"events__all":[
{
"category__category":"Gallery",
"eventid":16200847,
"description":"A Wider View, curated by Onyx Fine Arts Collective, features 60 works by 23 artists of African descent.",
"title":"A Wider View",
"cost":"Free",
"category__slug":"gallery",
"slug":"a-wider-view"
}
],
"venue_name":"City Hall Lobby Gallery",
"venue_address":"600 4th Avenue, Seattle, WA 98104, USA",
"city_name":"Seattle",
"neighborhood_slug":"downtown",
"venue_slug":"city-hall-lobby-gallery"
}
},
{
"geometry":{
"type":"Point",
"coordinates":[
-122.348512,
47.6217233
]
},
"type":"Feature",
"id":42,
"properties":{
"city_slug":"seattle",
"neighborhood_name":"Downtown",
"events__all":[
{
"category__category":"Museums",
"eventid":15455000,
"description":"The Art of Video Games tackles a 40-year history, with a focus on video game as art form. Nerdy heartstrings will be tugged in this nostalgia-inducing retrospective, including everything from the Atari VCS to Playstation 3.",
"title":"The Art of Video Games",
"cost":"$20",
"category__slug":"museums",
"slug":"the-art-of-video-games"
},
{
"category__category":"Museums",
"eventid":15213972,
"description":"There's just something about the black leather jacket. It's a garment that invariably comes with context, that cannot help but be an icon. Worn to Be Wild: The Black Leather Jacket explores the evolution of the leather jacket from \"protective gear to revolutionary garb.\"",
"title":"Worn to Be Wild: The Black Leather Jacket",
"cost":"$20",
"category__slug":"museums",
"slug":"worn-to-be-wild-the-black-leather-jacket"
}
],
"venue_name":"Experience Music Project | Science Fiction Museum.",
"venue_address":"325 5th Avenue North, Seattle, WA 98109, USA",
"city_name":"Seattle",
"neighborhood_slug":"downtown",
"venue_slug":"experience-music-project-science-fiction-museum"
}
}
],
"bbox":[
-122.348512,
47.6035448,
-122.3233742,
47.6217233
]
}
I want to map this into a Collection called VenueEvents. VenueEvents contains models called JsonVenues, and each of these Venues then have contain a collection called EventSet, containing a number of Event models (side topic: is naming a model 'Event' a recipe for disaster?).
My models are outlined as such:
var Event = Backbone.Model.extend({
parse: function(response){
return {
id: response.eventid,
slug: response.slug,
title: repsonse.title,
description: response.description,
category: response.category__category,
cost: response.cost
}
}
});
var EventSet = Backbone.Collection.extend({
model: Event,
}
});
var JsonVenue = Backbone.Model.extend({
initialize: function(attributes) {
console.log(attributes)
},
parse: function(response){
// var eventSet = new EventSet(response.properties.events__all);
return {
name: response.properties.venue_name,
address: response.properties.venue_address,
neighborhood: response.properties.neighborhood_name,
//eventSet: eventSet
}
}
});
// Is this actually a model?
var VenueEvents = Backbone.Collection.extend({
model: JsonVenue,
url: '/json/',
parse: function(response){
return response.features;
}
});
The VenueEvents and JsonVenue objects get created as expected, with the exception that the response.properties.events__all object doesn't seem to make it's way to the JsonVenue model (which is where I'd expect to use it to create the EventSet collection). I've put a console.log(attributes) in the initialize parameter of the JsonVenue object and it shows that while all the other values within features.properties of a JsonVenue make its way to the model, the events__all does not.
Is there any reason why this would be happening? Is this method of loading nested JSON data into models even possible? In most examples, people are only including the id of the nested object in their JSON output, and then (I assume) building a model out of that object in another JSON string and relating them based on the ID. This seems like it would require more traffic between the server and client. I also see people side-loading data, is this the recommended method of relating models in a single API call?
Thanks!
Well.. Ive just tried your code, using:
new VenueEvents(json, {parse: true});
to create your collection. And... it works just fine it seems...
Still, Backbone-relational might have the behavior you want to simplify your code (this is just an assumption, I've never tested it myself, nor have had a real look at it).