Using knockout.mapping I try to map from JSON to ko-object. When trying to use more advanced solutions with mapping options I often got Uncaught TypeError: g is not a function
. I tried to figure out what is problem, but I am still confused.
Lets say I have pretty simple model:
var data1 = { // works
a: "a",
b: [
{ b1: "v1" },
{ b2: "v2" }
]
};
Now consuming this model works fine like this:
function viewModel ( data ) {
var self = this;
var a = ko.mapping.fromJS( data, { observe: "a"} );
return a;
}
var vm1 = viewModel( data1 ); // works
I have two properties (a
and b
) in my model, but I need only a
to be observable, while b
should just copied to the viewmodel.
Sadly it does not work so well, if data-structure gets deeper:
var data1 = { // works
a: "a",
b: [
{ b1: "v1" },
{ b2: "v2" }
]
};
var data2 = { // Uncaught TypeError: g is not a function
a: "a",
b: [
{ b1: { name: "v1" } },
{ b2: { name: "v2" } }
]
};
var data3 = { // Uncaught TypeError: g is not a function
a: "a",
b1: { name: "v1" },
b2: { name: "v2" }
};
var data4 = { // works
a: "a",
b1: "v1",
b2: "v2"
};
var data5 = { // works
a: "a",
b1: ["1::v1", "2::v1"],
b2: ["1::v2", "2::v2"]
};
function viewModel ( data ) {
var self = this;
var a = ko.mapping.fromJS( data, { observe: "a"} );
return a;
}
var vm1 = viewModel( data1 ); // works
var vm2 = viewModel( data2 ); // Uncaught TypeError: g is not a function
var vm3 = viewModel( data3 ); // Uncaught TypeError: g is not a function
var vm4 = viewModel( data4 ); // works
var vm5 = viewModel( data5 ); // works
It seems baffling that copying some structures works and other not. I understand, that problem jumps in, when there is multilevel hash somewhere in the tree.
Is this desired behavior or do I something wrong here? Why copying some structure works while other not?
I've succesfully reproduced the issue you mentioned:
var data1 = { // works
a: "a",
b: [
{ b1: "v1" },
{ b2: "v2" }
]
};
var data2 = { // Uncaught TypeError: g is not a function
a: "a",
b: [
{ b1: { name: "v1" } },
{ b2: { name: "v2" } }
]
};
function viewModel ( data ) {
var self = this;
var a = ko.mapping.fromJS( data, { observe: ["a"]} );
return a;
}
var vm1 = viewModel( data1 ); // works
var vm2 = viewModel( data2 ); // Uncaught TypeError: g is not a function
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-debug.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.js"></script>
As you can see, in my repro I include the non-minified versions of both libs. I stepped into it on the error, and it's here, in this code:
if(options.observe.length > 0 && ko.utils.arrayIndexOf(options.observe, fullPropertyName) == -1)
{
mappedRootObject[indexer] = value(); // <---- THIS LINE
options.copiedProperties[fullPropertyName] = true;
return;
}
That line struck me as odd, because typically with KO you need to explicitly need to "unwrap" values if you're unsure if they're a plain value or an observable.
So I went to the relevant source code on Github, but to my surprise, the above code from the CDNJS version is not there, it's different:
if(options.observe.length > 0 && ko.utils.arrayIndexOf(options.observe, fullPropertyName) == -1)
{
mappedRootObject[indexer] = ko.utils.unwrapObservable(value); // <-- DIFFERENT
options.copiedProperties[fullPropertyName] = true;
return;
}
That line is the way I'd expect it to be.
Then again, the most recent debug build has the buggy/first version again.
Then some more digging, I see there's issue #137 with exactly this problem. That issue ends with the same conclusion I reached: the fix is not included in the 2.4.1 build. There will not likely soon be new builds: as you can see in the project's readme this project is currently not actively being maintained.
Bottom line: you'll need to either work around this situation, or create your own custom build of mapping with this problem fixed.