Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why knockout.mapping fails on some data-structures?

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?

like image 668
w.k Avatar asked Mar 18 '26 01:03

w.k


1 Answers

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.

like image 105
Jeroen Avatar answered Mar 20 '26 13:03

Jeroen



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!