Purpose: Convert Parse Objects to plain JavaScript objects with automatic pointer stripping and nested object resolution.
Source: actinium-core/lib/utils/serialize.js:1-35
Serialize Parse Object to plain JSON with pointer type cleanup.
Parameters:
data- Parse Object, array of Parse Objects, or any object with.toJSON()method
Returns: Plain JavaScript object with:
- All Parse Object data converted to JSON
- Nested Parse Objects automatically serialized
- Pointer
__typefields removed - ACL preserved (if present)
Signature:
const serialize = (data) => {
if (!data || typeof data.toJSON === 'undefined') return data;
const obj = data.toJSON();
Object.entries(obj).forEach(([key, value]) => {
if (value && typeof value.toJSON !== 'undefined') {
obj[key] = value.toJSON();
}
// Strip pointers
if (op.has(obj, [key, '__type'])) op.del(obj, [key, '__type']);
});
return obj;
};Source: serialize.js:19-32
serialize(null); // null
serialize(undefined); // undefined
serialize('string'); // 'string'
serialize(123); // 123
serialize({}); // {} (no toJSON method, returns as-is)Source: serialize.js:20
const user = await new Parse.Query('_User').first();
const serialized = Actinium.Utils.serialize(user);
// Before serialize
{
className: '_User',
id: 'abc123',
attributes: { username: 'john', email: 'john@example.com' },
_toFullJSON: [Function],
toJSON: [Function]
}
// After serialize
{
objectId: 'abc123',
username: 'john',
email: 'john@example.com',
createdAt: '2025-01-15T10:30:00.000Z',
updatedAt: '2025-01-15T10:30:00.000Z',
ACL: { /* ACL object */ }
}const content = await new Parse.Query('Content')
.include('type')
.include('user')
.first();
const serialized = Actinium.Utils.serialize(content);
// Before serialize
{
title: 'Hello World',
type: Parse.Object, // Parse Object instance
user: Parse.Object // Parse Object instance
}
// After serialize
{
title: 'Hello World',
type: { // Resolved to plain object
objectId: 'xyz789',
machineName: 'blog',
label: 'Blog Post'
},
user: { // Resolved to plain object
objectId: 'abc123',
username: 'john'
}
}Source: serialize.js:22-25
Parse SDK adds __type: 'Pointer' metadata to pointer fields. serialize() removes these:
// Raw Parse.Object.toJSON()
{
type: {
__type: 'Pointer',
className: 'Type',
objectId: 'xyz789'
}
}
// After Actinium.Utils.serialize()
{
type: {
objectId: 'xyz789',
machineName: 'blog',
label: 'Blog Post'
// __type removed
}
}Source: serialize.js:27-28
ACL objects are preserved (not stripped):
const content = await new Parse.Query('Content').first();
const serialized = Actinium.Utils.serialize(content);
console.log(serialized.ACL);
// {
// "*": { read: false, write: false },
// "abc123": { read: true, write: true },
// "role:administrator": { read: true, write: true }
// }Parse Relation fields are NOT automatically fetched or resolved:
const content = await new Parse.Query('Content').first();
const serialized = Actinium.Utils.serialize(content);
console.log(serialized.taxonomy);
// {
// __type: 'Relation',
// className: 'Taxonomy'
// }
// Relation not fetched, metadata preservedWorkaround: Manually query and attach relation data:
const content = await new Parse.Query('Content').first();
const taxonomies = await content.relation('taxonomy').query().find();
const serialized = Actinium.Utils.serialize(content);
serialized.taxonomy = taxonomies.map(Actinium.Utils.serialize);Actinium.Cloud.define('MY_PLUGIN', 'get-content', async (req) => {
const content = await new Parse.Query('Content')
.include('type')
.include('user')
.first(Actinium.Utils.MasterOptions());
// Serialize before returning
return Actinium.Utils.serialize(content);
});
// Client receives plain object (no Parse SDK dependency)
const data = await Actinium.Cloud.run('get-content');
console.log(data.type.machineName); // 'blog' (not Parse.Object)Usage: actinium-syndicate/sdk.js:101,126, actinium-route/sdk.js:29,59
const users = await new Parse.Query('_User').find();
// Map and serialize
const serialized = users.map(Actinium.Utils.serialize);
// Or serialize containing object
const result = {
count: users.length,
results: users.map(Actinium.Utils.serialize)
};Usage: actinium-user.js:243, actinium-plugable.js:277
// Bad - Parse Objects can't be cached directly
Actinium.Cache.set('user', parseUserObject); // May cause issues
// Good - serialize before caching
Actinium.Cache.set('user', Actinium.Utils.serialize(parseUserObject));
// Retrieve and reconstruct if needed
const cached = Actinium.Cache.get('user');
if (cached) {
const user = new Parse.Object('_User');
user.id = cached.objectId;
// Set attributes from cached data
}Usage: Cache documentation example: MEMORY_CACHE_SYSTEM.md:756
Actinium.Hook.register('search-index-item-normalize', async (item, content) => {
// Serialize Parse Object for indexing
const serialized = Actinium.Utils.serialize(content);
// Flatten to plain text for search
return {
...serialized,
searchText: extractPlainText(serialized.data.body)
};
});Usage: actinium-search/search-lunr-plugin.js:76, actinium-search/sdk.js:24
async function listContent(params) {
const query = new Parse.Query('Content');
query.include('type');
query.include('user');
const count = await query.count();
const results = await query.find();
return {
count,
page: params.page,
pages: Math.ceil(count / params.limit),
results: results.map(Actinium.Utils.serialize) // Clean response
};
}Usage: Pagination patterns: PAGINATION_STRATEGIES.md:244,305,614
const content = await new Parse.Query('Content')
.include('type')
.include('user')
.first();
// Single serialize call handles all nesting
const serialized = Actinium.Utils.serialize(content);
// All nested Parse Objects resolved
console.log(serialized.type.machineName); // Direct access
console.log(serialized.user.username); // No Parse.Object wrapperActinium.Plugin.register(PLUGIN, false);
const plugins = await new Parse.Query('Plugin').find();
return {
plugins: plugins.map(Actinium.Utils.serialize)
};Usage: actinium-plugable.js:277,339
| Feature | serialize() | .toJSON() |
|---|---|---|
| Nested Objects | Auto-resolves | Returns Parse.Object |
| Pointer Cleanup | Strips __type |
Preserves __type |
| Null Safety | Returns null/undefined | Throws on null |
| Relations | Metadata only | Metadata only |
| ACL | Preserved | Preserved |
Example:
const content = await query.include('type').first();
// .toJSON() - nested Parse Object not serialized
const json = content.toJSON();
console.log(json.type);
// { __type: 'Pointer', className: 'Type', objectId: 'xyz' }
// serialize() - nested object fully resolved
const serialized = Actinium.Utils.serialize(content);
console.log(serialized.type);
// { objectId: 'xyz', machineName: 'blog', label: 'Blog Post' }| Feature | serialize() | JSON.stringify() |
|---|---|---|
| Parse Objects | Converts to plain | Throws error |
| Circular Refs | Not handled | Throws error |
| Custom toJSON | Uses method | Uses method |
| Pointer Cleanup | Yes | No |
Example:
const user = await query.first();
// JSON.stringify() on Parse Object throws
JSON.stringify(user); // Error: Converting circular structure
// serialize() first
JSON.stringify(Actinium.Utils.serialize(user)); // Works// Good
Actinium.Cloud.define('get-data', async () => {
const data = await query.first();
return Actinium.Utils.serialize(data); // Clean response
});
// Bad - returns Parse.Object
Actinium.Cloud.define('get-data', async () => {
const data = await query.first();
return data; // Client receives Parse SDK dependency
});// Good
return results.map(Actinium.Utils.serialize);
// Bad - only serializes array wrapper
return Actinium.Utils.serialize(results); // Array itself, not items// Good - pointers resolved
const content = await query
.include('type')
.include('user')
.first();
const serialized = Actinium.Utils.serialize(content);
// Bad - pointers not resolved
const content = await query.first();
const serialized = Actinium.Utils.serialize(content);
console.log(serialized.type); // { __type: 'Pointer', objectId: 'xyz' }// Bad - double serialization returns same object
const serialized = Actinium.Utils.serialize(content);
const doubled = Actinium.Utils.serialize(serialized);
// doubled === serialized (no toJSON method on plain object)
// Good - serialize once
const serialized = Actinium.Utils.serialize(content);// Good - cache plain objects
const serialized = Actinium.Utils.serialize(user);
Actinium.Cache.set('user', serialized);
// Bad - cache Parse Objects (may cause issues)
Actinium.Cache.set('user', user);// serialize() is null-safe
const data = await query.first(); // May be undefined
const serialized = Actinium.Utils.serialize(data); // Returns undefined, no errorProblem: Relation fields show metadata only, not actual related objects
const content = await query.first();
const serialized = Actinium.Utils.serialize(content);
console.log(serialized.taxonomy);
// { __type: 'Relation', className: 'Taxonomy' }
// NOT array of taxonomiesSolution: Manually fetch relations before serializing
const content = await query.first();
const taxonomies = await content.relation('taxonomy').query().find();
const serialized = Actinium.Utils.serialize(content);
serialized.taxonomy = taxonomies.map(Actinium.Utils.serialize);Problem: Pointer fields show objectId only without .include()
// Without include
const content = await query.first();
const serialized = Actinium.Utils.serialize(content);
console.log(serialized.type);
// { __type: 'Pointer', className: 'Type', objectId: 'xyz' }
// With include
const content = await query.include('type').first();
const serialized = Actinium.Utils.serialize(content);
console.log(serialized.type);
// { objectId: 'xyz', machineName: 'blog', label: 'Blog Post' }Source: Pointer cleanup only removes __type, doesn't fetch data
Problem: Serializing array doesn't serialize items
const users = await query.find();
// Bad - array itself has no toJSON
const serialized = Actinium.Utils.serialize(users);
console.log(serialized); // Original array (unchanged)
// Good - map items
const serialized = users.map(Actinium.Utils.serialize);Problem: If Parse Object has circular references, serialization may fail
// Rare, but possible with custom fields
content.set('self', content);
const serialized = Actinium.Utils.serialize(content);
// May cause issues if .toJSON() doesn't handle circular refsSolution: Avoid circular references in Parse Objects
Problem: Objects with custom .toJSON() may not serialize as expected
class CustomClass {
toJSON() {
return { custom: 'data' };
}
}
const obj = new CustomClass();
const serialized = Actinium.Utils.serialize(obj);
console.log(serialized); // { custom: 'data' }
// Nested objects within toJSON NOT auto-serializedSolution: Ensure custom .toJSON() handles nested serialization
Problem: Parse File objects serialize to URL strings
content.set('file', parseFile);
const serialized = Actinium.Utils.serialize(content);
console.log(serialized.file);
// { name: 'file.jpg', url: 'https://...', __type: 'File' }Parse File .toJSON() includes URL, __type remains (not a Pointer, so not stripped)
Problem: ACL serializes to plain object, not Parse.ACL instance
const serialized = Actinium.Utils.serialize(content);
// Can't call Parse.ACL methods
serialized.ACL.setReadAccess(userId, true); // Error: not a method
// ACL is plain object
console.log(serialized.ACL);
// { "userId": { read: true, write: true } }Solution: Reconstruct Parse.ACL if needed
const acl = new Parse.ACL();
Object.entries(serialized.ACL).forEach(([key, perms]) => {
if (perms.read) acl.setReadAccess(key, true);
if (perms.write) acl.setWriteAccess(key, true);
});Serialize responses for clean client consumption:
Actinium.Cloud.define('PLUGIN', 'function-name', async (req) => {
const result = await query.first();
return Actinium.Utils.serialize(result);
});Usage: Every cloud function that returns Parse Objects
Serialize before caching to avoid Parse Object issues:
const data = Actinium.Utils.serialize(parseObject);
Actinium.Cache.set('key', data);Related: MemoryCache System
Serialize for indexing and plain-text extraction:
Actinium.Hook.register('search-index-item-normalize', (item, content) => {
return Actinium.Utils.serialize(content);
});Related: Search System
Serialize content for cross-site API responses:
const content = await query.first();
return Actinium.Utils.serialize(content);Related: Syndicate System
Serialize paginated results:
return {
count,
page,
pages,
results: items.map(Actinium.Utils.serialize)
};Related: Pagination Strategies
const parseObject = await query.first();
console.log('Before:', parseObject);
console.log('Has toJSON:', typeof parseObject.toJSON);
const serialized = Actinium.Utils.serialize(parseObject);
console.log('After:', serialized);
console.log('Has toJSON:', typeof serialized.toJSON);const parseObject = await query.include('type').first();
const manualJSON = parseObject.toJSON();
const serialized = Actinium.Utils.serialize(parseObject);
console.log('toJSON type:', manualJSON.type);
// { __type: 'Pointer', className: 'Type', objectId: 'xyz' }
console.log('serialized type:', serialized.type);
// { objectId: 'xyz', machineName: 'blog', label: 'Blog Post' }const serialized = Actinium.Utils.serialize(parseObject);
// Verify no __type fields
function hasPointerType(obj, path = '') {
for (const [key, value] of Object.entries(obj)) {
if (key === '__type') {
console.log('Found __type at:', path + key);
}
if (value && typeof value === 'object') {
hasPointerType(value, path + key + '.');
}
}
}
hasPointerType(serialized);const content = await query
.include('type')
.include('user')
.first();
const serialized = Actinium.Utils.serialize(content);
// Check nested objects are plain
console.log(serialized.type.constructor.name); // 'Object' (not Parse.Object)
console.log(serialized.user.constructor.name); // 'Object'- Cloud Function Patterns - Using serialize in cloud functions
- MemoryCache System - Caching serialized objects
- Actinium Search System - Serialization for indexing
- Actinium Syndicate System - API response serialization
- Pagination Strategies - Serializing paginated results
// actinium-core/lib/utils/serialize.js:19-32
const serialize = (data) => {
if (!data || typeof data.toJSON === 'undefined') return data;
const obj = data.toJSON();
Object.entries(obj).forEach(([key, value]) => {
if (value && typeof value.toJSON !== 'undefined') {
obj[key] = value.toJSON();
}
// strip pointers
if (op.has(obj, [key, '__type'])) op.del(obj, [key, '__type']);
});
return obj;
};
export default serialize;Export: actinium-core/lib/utils/index.js → Actinium.Utils.serialize
Serialization is used extensively throughout the framework:
- Cloud Functions: 15+ usages in syndicate/route/user/plugin SDKs
- Cache Storage: Recommended pattern for all Parse Object caching
- Search Indexing: Required for Lunr.js indexing
- API Responses: Standard pattern for all cloud function returns
- Pagination: Used in all paginated query results
Total Framework Usage: 20+ files, 40+ direct calls
// Lightweight - single toJSON call per object
const serialized = Actinium.Utils.serialize(content);
// O(n) where n = number of fields + nested objects// Efficient - fetch with include, serialize once
const content = await query
.include('type')
.include('user')
.first();
const serialized = Actinium.Utils.serialize(content);
// Inefficient - multiple fetches
const content = await query.first();
const type = await content.get('type').fetch();
const user = await content.get('user').fetch();
const serialized = Actinium.Utils.serialize(content);// Efficient - map serialization
const items = await query.find();
const serialized = items.map(Actinium.Utils.serialize);
// Inefficient - loop with individual serialize
const serialized = [];
for (const item of items) {
serialized.push(Actinium.Utils.serialize(item));
}
// Both are O(n), but map is cleaner and faster// Type-safe serialization
interface SerializedContent {
objectId: string;
title: string;
type: {
objectId: string;
machineName: string;
};
}
const content = await query.include('type').first();
const serialized: SerializedContent = Actinium.Utils.serialize(content);Note: TypeScript types not included in actinium-core, define manually for type safety.