Skip to content

Knockout.js Mapping Plugin — Converting JSON to Observable ViewModels

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Knockout.js Mapping Plugin. We cover key concepts, practical examples, and best practices to help you master this topic.

Knockout.js mapping plugin automatically converts plain JavaScript objects into ViewModels with observable properties, computed observables, and observable arrays, eliminating manual conversion code.

What You'll Learn

  • Installing and using the mapping plugin
  • Converting JSON to observables automatically
  • Customizing mapping with options
  • Handling nested objects and arrays
  • Updating existing ViewModels from new data

Why It Matters

API responses return plain JSON. Creating observables for each property manually is tedious and error-prone. The mapping plugin automates this conversion, and its update capabilities let you refresh ViewModels without losing UI state.

Real-World Use

A dashboard that polls a REST API every 30 seconds for updated metrics. The mapping plugin converts the JSON response to an observable ViewModel, and subsequent updates merge new data while preserving computed values and non-mapped properties.

Mapping Flow

flowchart LR
    A[JSON API Response] --> B[ko.mapping.fromJS]
    B --> C[Observable ViewModel]
    C --> D[UI Bindings]
    D --> E[User Edits]
    E --> F[ko.mapping.toJSON]
    F --> G[Updated JSON]
    G --> H[Send to Server]
    style B fill:#e6f3ff,stroke:#4a90d9,stroke-width:2px

Installing the Mapping Plugin

<!-- After Knockout core -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>

<!-- Or with npm -->
<!-- npm install knockout-mapping -->

Basic Usage: Converting JSON to Observables

// Sample API response
var userData = {
  id: 1,
  name: 'Alice Johnson',
  email: 'alice@example.com',
  age: 30,
  address: {
    street: '123 Main St',
    city: 'Portland',
    zip: '97201'
  },
  tags: ['developer', 'designer'],
  isActive: true
};

// Convert to observable ViewModel
var viewModel = ko.mapping.fromJS(userData);

// Read values
console.log(viewModel.name());        // Output: Alice Johnson
console.log(viewModel.age());         // Output: 30
console.log(viewModel.address().city()); // Output: Portland
console.log(viewModel.tags()[0]);     // Output: developer

// Write values
viewModel.name('Bob Smith');
viewModel.address().city('Seattle');
viewModel.tags.push('manager');

console.log(viewModel.name());        // Output: Bob Smith
console.log(viewModel.address().city()); // Output: Seattle

Expected output: All properties are converted to observables. Nested objects become nested ViewModels with observable properties. Arrays become observableArrays.

Creating the ViewModel from the Start

function AppViewModel() {
  var self = this;

  // Load user data from API
  self.user = ko.observable();

  self.loadUser = function(userId) {
    // Simulate API call
    var json = {
      id: userId,
      name: 'Alice Johnson',
      email: 'alice@example.com'
    };
    self.user(ko.mapping.fromJS(json));
  };

  self.saveUser = function() {
    var json = ko.mapping.toJSON(self.user());
    console.log('Saving to server:', json);
    // POST json to API...
  };
}

Updating an Existing ViewModel

The mapping plugin can update an existing ViewModel with new data, preserving any non-mapped properties:

var viewModel = ko.mapping.fromJS({
  name: 'Alice',
  age: 30,
  status: 'active'
});

// Later, receive updated data from server
var updatedData = {
  name: 'Alice Johnson',  // Updated
  age: 30,                 // Same
  status: 'active',        // Same
  lastLogin: '2026-06-28'  // New field
};

// Update the existing ViewModel
ko.mapping.fromJS(updatedData, viewModel);

console.log(viewModel.name());       // Output: Alice Johnson (updated)
console.log(viewModel.lastLogin());  // Output: 2026-06-28 (added)

Expected output: The name property updates to the new value. The lastLogin property is added as a new observable. The ViewModel reference remains the same — UI bindings continue to work without re-rendering.

Customizing Mapping with Options

Control how the mapping plugin handles specific properties:

var mappingOptions = {
  // Specific property mapping
  'name': {
    create: function(options) {
      return ko.observable(options.data.toUpperCase());
    }
  },
  // Ignore certain properties
  'ignore': ['password', 'internalId'],
  // Copy properties as plain values (not observables)
  'copy': ['id', 'createdAt'],
  // Include specific properties only
  'include': ['name', 'email', 'role'],
  // Observe existing observable changes
  'observe': ['status', 'items']
};

var viewModel = ko.mapping.fromJS(apiResponse, mappingOptions);

Nested Mapping with Custom Options

var orderData = {
  id: 101,
  customer: {
    id: 1,
    name: 'Alice',
    loyaltyPoints: 1500
  },
  items: [
    { productId: 1, name: 'Laptop', price: 999, quantity: 1 },
    { productId: 2, name: 'Mouse', price: 25, quantity: 2 }
  ],
  status: 'pending'
};

var mapping = {
  'items': {
    key: function(item) {
      return ko.utils.unwrapObservable(item.productId);
    },
    create: function(options) {
      return new CartItem(options.data);
    }
  },
  'customer.loyaltyPoints': {
    update: function(options) {
      // Custom update logic
      return options.data * 2; // Double loyalty points
    }
  }
};

function CartItem(data) {
  ko.mapping.fromJS(data, {}, this);
  this.total = ko.pureComputed(function() {
    return this.price() * this.quantity();
  }, this);
}

var viewModel = ko.mapping.fromJS(orderData, mapping);

Expected output: Items are mapped using the key function for identity tracking. The create option constructs a CartItem instance with a computed total. Customer loyalty points are doubled during mapping.

Using key for Array Identity

The key option helps the mapping plugin match items between old and new data arrays:

var viewModel = ko.mapping.fromJS({
  products: [
    { id: 1, name: 'Laptop', price: 999 },
    { id: 2, name: 'Mouse', price: 25 }
  ]
});

// Later, update with new data
ko.mapping.fromJS({
  products: [
    { id: 1, name: 'Laptop', price: 899 },  // Price changed
    { id: 3, name: 'Keyboard', price: 75 }   // New item
  ]
}, {
  'products': {
    key: function(data) {
      return ko.utils.unwrapObservable(data.id);
    }
  }
}, viewModel);

Expected output: Product 1's price updates to 899 (item matched by id). Product 2 is removed from the array. Product 3 is added as a new item. Without the key option, the entire array would be replaced.

Converting ViewModel Back to JSON

var viewModel = ko.mapping.fromJS({
  name: 'Alice',
  age: 30,
  preferences: { theme: 'dark', notifications: true }
});

// Modify
viewModel.name('Bob');
viewModel.preferences().theme('light');

// Convert to plain JSON
var json = ko.mapping.toJSON(viewModel);
console.log(json);
// Output: {"name":"Bob","age":30,"preferences":{"theme":"light","notifications":true}}

// To plain JavaScript object (not JSON string)
var js = ko.mapping.toJS(viewModel);

Common Mistakes

  1. Calling fromJS on an already mapped ViewModel - Passing an existing ViewModel to fromJS without proper options creates nested observables-of-observables. Use fromJS(newData, {}, existingVM) to update.

  2. Not using key for array identity - Without key, the mapping plugin replaces the entire array on update, causing UI flicker and lost state on individual items.

  3. Forgetting to add mapping plugin - The mapping plugin is separate from Knockout core. Include it as a separate script tag or npm package.

  4. Mapping sensitive fields - Properties like password, token, and secret should be in the ignore list to prevent accidental exposure in the ViewModel.

  5. Overwriting custom computed observables - When updating, the mapping plugin replaces ViewModel properties. Use the update option or custom mapping to preserve computed values.

Practice Questions

  1. What is the primary purpose of the Knockout mapping plugin?
  2. How do you update an existing ViewModel with new JSON data without losing UI state?
  3. What does the key option do when mapping arrays?
  4. How do you convert a mapped ViewModel back to a JSON string?
  5. What option would you use to prevent certain properties from becoming observables?

Challenge: Build a contact manager that loads contacts from a JSON array, maps them to observable ViewModels, displays them in a list, and supports inline editing. When the user clicks Save, convert the ViewModel back to JSON and log it to the console.

FAQ

Does the mapping plugin support dates?

Dates are mapped as strings or numbers by default. Use a custom create option to convert date strings to Date objects during mapping.

Can I use the mapping plugin with nested arrays?

Yes, nested arrays are mapped recursively. Each nested array becomes an observableArray, and each item is mapped according to the same rules.

Does the mapping plugin handle circular references?

No. Circular references cause infinite recursion and a stack overflow. Ensure your data structure has no circular references before mapping.

Can I use the mapping plugin without Knockout?

No, the mapping plugin depends on Knockout core. It is specifically designed to create Knockout observables from plain objects.

Is the mapping plugin maintained for Knockout 3.x?

The mapping plugin (v2.4.1+) works with Knockout 3.x and is stable. It is not under active development but remains reliable.

Mini Project

Build a user profile editor that loads JSON from a simulated API, maps it to an observable ViewModel, displays editable fields (name, email, bio, preferences), and converts the ViewModel back to JSON on Save.

What's Next

Extend observable behavior with observable extenders to add custom functionality like logging, validation, or Rate Limiting to your ViewModel properties.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro