Knockout.js Mapping Plugin — Converting JSON to Observable ViewModels
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
Calling fromJS on an already mapped ViewModel - Passing an existing ViewModel to
fromJSwithout proper options creates nested observables-of-observables. UsefromJS(newData, {}, existingVM)to update.Not using
keyfor array identity - Withoutkey, the mapping plugin replaces the entire array on update, causing UI flicker and lost state on individual items.Forgetting to add mapping plugin - The mapping plugin is separate from Knockout core. Include it as a separate script tag or npm package.
Mapping sensitive fields - Properties like
password,token, andsecretshould be in theignorelist to prevent accidental exposure in the ViewModel.Overwriting custom computed observables - When updating, the mapping plugin replaces ViewModel properties. Use the
updateoption or custom mapping to preserve computed values.
Practice Questions
- What is the primary purpose of the Knockout mapping plugin?
- How do you update an existing ViewModel with new JSON data without losing UI state?
- What does the
keyoption do when mapping arrays? - How do you convert a mapped ViewModel back to a JSON string?
- 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
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