Map JSON data to Knockout observableArray with specific view model type

Is there a way to map a JSON data object to an observable array and then in turn have each item of the observable array be initialized into a specific type of view model?

I've looked at all of knockout's documentation along with the knockout and mapping examples here and I can't find any answer that works for what I'm after.

So, I have the following JSON data:

    var data = {
    state : {
        name : 'SD',
        cities : [{
            name : 'Sioux Falls',
            streets : [{
                number : 1
            }, {
                number : 3
            }]
        }, {
            name : 'Rapid City',
            streets : [{
                number : 2
            }, {
                number : 4
            }]
        }]
    }
};

And I have the following view models:

var StateViewModel = function(){
    this.name = ko.observable();
    this.cities = ko.observableArray([new CityViewModel()]);
}

var CityViewModel = function(){
    this.name = ko.observable();
    this.streets = ko.observableArray([new StreetViewModel()]);
}

var StreetViewModel = function(){
    this.number = ko.observable();
}

Is it possible, with the given data structure and using knockout's mapping plugin, to have the resulting StateViewModel contain an observableArray populated with 2 CityViewModels, and each CityViewModel containing an observableArray populated with 2 StreetViewModels?

Currently using the mapping plugin I'm able to get it to map to a StateViewModel, but the 'cities' and 'streets' collections are populated with generic objects instead of instances of my City and Street view models.

They end up with the correct observable properties and values on them, they're just not instances of my view models, which is what I'm after.


Solution 1:

Check this http://jsfiddle.net/pTEbA/268/

Object.prototype.getName = function() { 
   var funcNameRegex = /function (.{1,})\(/;
   var results = (funcNameRegex).exec((this).constructor.toString());
   return (results && results.length > 1) ? results[1] : "";
};

function StateViewModel(data){
    this.name = ko.observable();
    ko.mapping.fromJS(data, mapping, this);
}

function CityViewModel(data) {
    this.name = ko.observable();
    ko.mapping.fromJS(data, mapping, this);
}

function StreetViewModel(data) {
    this.name = ko.observable();
    ko.mapping.fromJS(data, mapping, this);
}

var mapping = {
    'cities': {
        create: function(options) {
            return new CityViewModel(options.data);
        }
    },
    'streets': {
        create: function(options) {
            return new StreetViewModel(options.data);
        }
    }
}


var data = { state: {name:'SD', cities:[{name:'Sioux Falls',streets:[{number:1},{number:3}]},
                                        {name:'Rapid City',streets:[{number:2},{number:4}]}]}};

var vm = new StateViewModel(data.state)
console.log(vm);
console.log(vm.getName());
console.log(vm.cities());
console.log(vm.cities()[0].getName());
console.log(vm.cities()[0].streets());
console.log(vm.cities()[0].streets()[0].getName());
​