Skip to content

Commit

Permalink
V2 - uses precise calculation for node.js, and separate implementatio…
Browse files Browse the repository at this point in the history
…n for the browser. (#70)

* Implemented v2.0, using Buffer.from(objectToString) method to convert the string representation of the object to a buffer and then it uses the byteLength property to obtain the size of the buffer in bytes

* Updated npm badge

* Got rid of v1

* Preserved TS type

* Simplified and modularised the code

* Added code coverage

* Added badges

* Added coding standards section
  • Loading branch information
miktam authored Jan 24, 2023
1 parent fad2ede commit c8675b6
Show file tree
Hide file tree
Showing 10 changed files with 6,804 additions and 1,029 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ npm-debug.log
node_modules
.idea
.vscode

.nyc_output
coverage.lcov
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ node_js:
- v12
- v13
- v14
- v16
94 changes: 53 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
## object-sizeof

[![Build Status](https://travis-ci.org/miktam/sizeof.svg?branch=master)](https://travis-ci.org/miktam/sizeof) [![Dependency Status](https://david-dm.org/miktam/sizeof.svg)](https://david-dm.org/miktam/sizeof)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fmiktam%2Fsizeof.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fmiktam%2Fsizeof?ref=badge_shield)
[![Build Status](https://travis-ci.org/miktam/sizeof.svg?branch=master)](https://travis-ci.org/miktam/sizeof) ![GitHub contributors](https://img.shields.io/github/contributors/miktam/sizeof) [![NPM](https://img.shields.io/npm/dy/object-sizeof)](https://img.shields.io/npm/dy/object-sizeof) [![codecov](https://codecov.io/gh/miktam/sizeof/branch/master/graph/badge.svg?token=qPHxmWpC1K)](https://codecov.io/gh/miktam/sizeof)

### Get size of a JavaScript object in Bytes - version 2.x

New version uses the Buffer.from(objectToString) method to convert the string representation of the object to a buffer and then it uses the byteLength property to obtain the size of the buffer in bytes.
Note that this method only work in Node.js environment.

For everything else, the calculation takes an object as an argument and uses a combination of recursion and a stack to iterate through all of its properties, adding up the number of bytes for each data type it encounters.

[![NPM](https://nodei.co/npm/object-sizeof.png?downloads=true&downloadRank=true)](https://nodei.co/npm/object-sizeof/)
Please note that this function will not work on all cases, specially when dealing with complex data structures or when the object contains functions.

### Get size of a JavaScript object in Bytes
### Coding standards

JavaScript does not provide sizeof (like in C), and programmer does not need to care about memory allocation/deallocation.
Project uses [JavaScript Standard Style](https://standardjs.com/).
Code coverage reports done using Codecov.io.

However, according to [ECMAScript Language Specification](http://www.ecma-international.org/ecma-262/5.1/), each String value is represented by 16-bit unsigned integer, Number uses the double-precision 64-bit format IEEE 754 values including the special "Not-a-Number" (NaN) values, positive infinity, and negative infinity.
### Get size of a JavaScript object in Bytes - version 1.x

Having this knowledge, the module calculates how much memory object will allocate.
JavaScript does not provide sizeof (like in C), and programmer does not need to care about memory allocation/deallocation.

However, according to [ECMAScript Language Specification](http://www.ecma-international.org/ecma-262/5.1/), each String value is represented by 16-bit unsigned integer, Number uses the double-precision 64-bit format IEEE 754 values including the special "Not-a-Number" (NaN) values, positive infinity, and negative infinity.

Having this knowledge, the module calculates how much memory object will allocate.

### Limitations
Please note, that V8 which compiles the JavaScript into native machine code, is not taken into account, as the compiled code is additionally heavily optimized.

Please note, that V8 which compiles the JavaScript into native machine code, is not taken into account, as the compiled code is additionally heavily optimized.

### Installation

Expand All @@ -25,45 +37,45 @@ Please note, that V8 which compiles the JavaScript into native machine code, is
#### ES5

```javascript
var sizeof = require('object-sizeof')

// 2B per character, 6 chars total => 12B
console.log(sizeof({abc: 'def'}))

// 8B for Number => 8B
console.log(sizeof(12345))

var param = {
'a': 1,
'b': 2,
'c': {
'd': 4
}
var sizeof = require('object-sizeof')

// 2B per character, 6 chars total => 12B
console.log(sizeof({ abc: 'def' }))

// 8B for Number => 8B
console.log(sizeof(12345))

var param = {
a: 1,
b: 2,
c: {
d: 4
}
// 4 one two-bytes char strings and 3 eighth-bytes numbers => 32B
console.log(sizeof(param))
}
// 4 one two-bytes char strings and 3 eighth-bytes numbers => 32B
console.log(sizeof(param))
```

#### ES6

```javascript
import sizeof from 'object-sizeof'

// 2B per character, 6 chars total => 12B
console.log(sizeof({abc: 'def'}))

// 8B for Number => 8B
console.log(sizeof(12345))

const param = {
'a': 1,
'b': 2,
'c': {
'd': 4
}
import sizeof from 'object-sizeof'

// 2B per character, 6 chars total => 12B
console.log(sizeof({ abc: 'def' }))

// 8B for Number => 8B
console.log(sizeof(12345))

const param = {
a: 1,
b: 2,
c: {
d: 4
}
// 4 one two-bytes char strings and 3 eighth-bytes numbers => 32B
console.log(sizeof(param))
}
// 4 one two-bytes char strings and 3 eighth-bytes numbers => 32B
console.log(sizeof(param))
```

### Licence
Expand All @@ -72,4 +84,4 @@ The MIT License (MIT)

Copyright (c) 2015, Andrei Karpushonak aka @miktam

[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fmiktam%2Fsizeof.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fmiktam%2Fsizeof?ref=badge_large)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fmiktam%2Fsizeof.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fmiktam%2Fsizeof?ref=badge_shield)
1 change: 1 addition & 0 deletions byte_size.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
module.exports = {
STRING: 2,
BOOLEAN: 4,
BYTES: 4,
NUMBER: 8
}
41 changes: 25 additions & 16 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@

'use strict'

var ECMA_SIZES = require('./byte_size')
var Buffer = require('buffer/').Buffer
const ECMA_SIZES = require('./byte_size')
const Buffer = require('buffer/').Buffer

function allProperties(obj) {
const isNodePlatform =
typeof process === 'object' && typeof require === 'function'

function allProperties (obj) {
const stringProperties = []
for (var prop in obj) {
stringProperties.push(prop)
for (const prop in obj) {
stringProperties.push(prop)
}
if (Object.getOwnPropertySymbols) {
var symbolProperties = Object.getOwnPropertySymbols(obj)
Array.prototype.push.apply(stringProperties, symbolProperties)
const symbolProperties = Object.getOwnPropertySymbols(obj)
Array.prototype.push.apply(stringProperties, symbolProperties)
}
return stringProperties
}
Expand All @@ -22,10 +25,10 @@ function sizeOfObject (seen, object) {
return 0
}

var bytes = 0
var properties = allProperties(object)
for (var i = 0; i < properties.length; i++) {
var key = properties[i]
let bytes = 0
const properties = allProperties(object)
for (let i = 0; i < properties.length; i++) {
const key = properties[i]
// Do not recalculate circular references
if (typeof object[key] === 'object' && object[key] !== null) {
if (seen.has(object[key])) {
Expand All @@ -50,22 +53,28 @@ function sizeOfObject (seen, object) {
}

function getCalculator (seen) {
return function calculator(object) {
return function calculator (object) {
if (Buffer.isBuffer(object)) {
return object.length
}

var objectType = typeof (object)
const objectType = typeof object
switch (objectType) {
case 'string':
return object.length * ECMA_SIZES.STRING
// https://stackoverflow.com/questions/68789144/how-much-memory-do-v8-take-to-store-a-string/68791382#68791382
return isNodePlatform
? 12 + 4 * Math.ceil(object.length / 4)
: object.length * ECMA_SIZES.STRING
case 'boolean':
return ECMA_SIZES.BOOLEAN
case 'number':
return ECMA_SIZES.NUMBER
case 'symbol':
case 'symbol': {
const isGlobalSymbol = Symbol.keyFor && Symbol.keyFor(object)
return isGlobalSymbol ? Symbol.keyFor(object).length * ECMA_SIZES.STRING : (object.toString().length - 8) * ECMA_SIZES.STRING
return isGlobalSymbol
? Symbol.keyFor(object).length * ECMA_SIZES.STRING
: (object.toString().length - 8) * ECMA_SIZES.STRING
}
case 'object':
if (Array.isArray(object)) {
return object.map(getCalculator(seen)).reduce(function (acc, curr) {
Expand Down
5 changes: 5 additions & 0 deletions indexv2.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* Calculates the approximate number of bytes that the provided object holds.
* @param object
*/
export default function sizeof<T>(object: T): number;
87 changes: 87 additions & 0 deletions indexv2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright 2023 ChatGPT Jan 9 Version
/* eslint-disable new-cap */ // to fix new Buffer.from
'use strict'
const ECMA_SIZES = require('./byte_size')

/**
* Size in bytes in a Node.js environment
* @param {*} obj
* @returns size in bytes, or -1 if JSON.stringify threw an exception
*/
function objectSizeNode (obj) {
let totalSize = 0
const errorIndication = -1
try {
const objectToString = JSON.stringify(obj)
const buffer = new Buffer.from(objectToString)
totalSize = buffer.byteLength
} catch (ex) {
console.error('Error detected, return ' + errorIndication, ex)
return errorIndication
}
return totalSize
}

/**
* Size in bytes in a browser environment
* @param {*} obj
* @returns size in bytes
*/
function objectSizeBrowser (obj) {
const objectList = []
const stack = [obj]
let bytes = 0

while (stack.length) {
const value = stack.pop()

if (typeof value === 'boolean') {
bytes += ECMA_SIZES.BYTES
} else if (typeof value === 'string') {
bytes += value.length * ECMA_SIZES.STRING
} else if (typeof value === 'number') {
bytes += ECMA_SIZES.NUMBER
} else if (typeof value === 'symbol') {
const isGlobalSymbol = Symbol.keyFor && Symbol.keyFor(obj)
if (isGlobalSymbol) {
bytes += Symbol.keyFor(obj).length * ECMA_SIZES.STRING
} else {
bytes += (obj.toString().length - 8) * ECMA_SIZES.STRING
}
} else if (typeof value === 'object' && objectList.indexOf(value) === -1) {
objectList.push(value)

for (const i in value) {
stack.push(value[i])
}
}
}
return bytes
}

/**
* Are we running in a Node.js environment
* @returns boolean
*/
function isNodeEnvironment () {
if (
typeof process !== 'undefined' &&
process.versions &&
process.versions.node
) {
return true
}
return false
}

module.exports = function (obj) {
let totalSize = 0

if (obj !== null && typeof obj === 'object' && isNodeEnvironment()) {
totalSize = objectSizeNode(obj)
} else {
totalSize = objectSizeBrowser(obj)
}

return totalSize
}
Loading

2 comments on commit c8675b6

@martin19
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi. Not sure if I understand correctly, but is the node.js path measuring the byte length of the stringified object? What is the relationship between this value and actual memory usage of an object in the js engine - seems like a generous upper bound to me. What about deep objects with circular references? Seems to me ChatGPT needs to do some more deep thinking on this ;)

@miktam
Copy link
Owner Author

@miktam miktam commented on c8675b6 Jan 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with the comment.
1.6.3 became such a mess, tests did not even pass, so it´s time to rebuild it, having better test structure, and code coverage.

Please sign in to comment.