Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Javascript

Replacement for ko.mapping.fromJS

0.00/5 (No votes)
7 May 2014CPOL 15.9K  
There is a problem with ko mapping and here is how to fix it

Introduction

About mapping business model to Knockout observable

Background

In my app I turn my (C#) business model with JSON converter into Javascript object. Then I use ko.mapping.fromJS to turn them into observable I ran into the fact that ko.mapping is inconsistent!
(If you don't know what are observables and/or ko.mapping, have a look at Knockout)

Allow me to illustrate, say I have this C# model

C#
class Address { string Street; }
class Person { Address Home; Address Work; }  

Which I can turn into this sample JSON data

JavaScript
var json = { Home: null, Work: { Street: "1 work street" } }

Which after ko.mapping will become

JavaScript
var kojson = ko.mapping.fromJS(json)
var json = { Home: ko.observable(), Work: { Street: ko.observable("1 work street") } }

See the problem? Home is now an observable property, whereas Work is a normal object (with observable properties)

Using the code

To solve this problem I wrote a replacement function, here is the (TypeScript) version

JavaScript
// unlike ko.mapping this will always map the same way, 
// i.e. all properties will be mapped to an observable (recursively)
function komapperToKO(src) {
    var mapped: Array<{ src; dst; }> = [];
    var map = function (obj) {
        obj = ko.unwrap(obj);
        if (obj === null
            || obj === undefined
            || typeof obj === "number"
            || typeof obj === "boolean"
            || typeof obj === "string"
            || typeof obj === "function"
            ) {
            return obj;
        }
        else if (obj instanceof Array) {
            var ares = [];
            for (var i = 0; i < obj.length; i++) {
                ares.push(map(obj[i]));
            }
            return ko.observableArray(ares);
        }
        else {
            var prev = mapped.enumerable().first(x => x.src === obj);
            if (prev)
                return prev.dst;
            var res = <any>{};
            mapped.push({ src: obj, dst: res });

            for (var p in obj) {
                var pv = ko.unwrap(obj[p]);
                if (typeof pv === "function") {
                    res[p] = pv;
                }
                else if (pv instanceof Array) {
                    res[p] = map(pv);
                }
                else {
                    res[p] = ko.observable(map(pv));
                }
            }
            return res;
        }
    }
    return map(src);
}

Points of Interest

A little dive inside Knockout

History

Keep a running update of any changes or improvements you've made here.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)