137

Previously, when I needed to store a number of related variables, I'd create a class.

function Item(id, speaker, country) {
  this.id = id;
  this.speaker = speaker;
  this.country = country;
}
var myItems = [new Item(1, 'john', 'au'), new Item(2, 'mary', 'us')];

But I'm wondering if this is a good practice. Are there any other, better ways to simulate a struct in JavaScript?

Mario Petrovic
  • 7,500
  • 14
  • 42
  • 62
nickf
  • 537,072
  • 198
  • 649
  • 721
  • 7
    There's a typo in the code - "spkr" – Nathan B Dec 27 '20 at 18:19
  • 1
    Is you concern memory efficiency? Or just type checking? For typechecking, typescript is definitely the way today. For memory efficiency, I'm still looking! https://stackoverflow.com/questions/1248302/how-to-get-the-size-of-a-javascript-object – Ciro Santilli OurBigBook.com Apr 28 '22 at 12:42

10 Answers10

201

The only difference between object literals and constructed objects are the properties inherited from the prototype.

var o = {
  'a': 3, 'b': 4,
  'doStuff': function() {
    alert(this.a + this.b);
  }
};
o.doStuff(); // displays: 7

You could make a struct factory.

function makeStruct(names) {
  var names = names.split(' ');
  var count = names.length;
  function constructor() {
    for (var i = 0; i < count; i++) {
      this[names[i]] = arguments[i];
    }
  }
  return constructor;
}

var Item = makeStruct("id speaker country");
var row = new Item(1, 'john', 'au');
alert(row.speaker); // displays: john
Markus Jarderot
  • 86,735
  • 21
  • 136
  • 138
  • I like this approach, however be careful if you use the closure compiler. The tuple can only be accessed as string in this case, because the properties are renamed. (At least in advanced mode) – kap Mar 24 '16 at 08:15
  • 3
    I was curious about the efficiency of this method over the object literals. – c0degeas Jul 05 '19 at 13:22
  • It is maybe also possible to use JS class. – SphynxTech Apr 04 '20 at 12:58
41

I always use object literals

{id: 1, speaker:"john", country: "au"}
vava
  • 24,851
  • 11
  • 64
  • 79
  • 3
    wouldn't that make it much harder to maintain (should you want to add a new field in the future), and also much more code (retyping "id", "speaker", "country" every time)? – nickf Feb 02 '09 at 06:53
  • 6
    It is exactly as maintainable as solution with classes because JavaScript doesn't care about number of arguments you call the function with. Retyping is not an issue if you using right tools like Emacs. And you can see what equals what which makes mistakes like swapping arguments obsolete. – vava Feb 02 '09 at 07:22
  • 5
    But the biggest pro is that you would write less code and it'll be cleaner :) – vava Feb 02 '09 at 07:24
  • 3
    @vava retyping is still an issue, as there are more jumps than copying the new ___ ( , , , ) archetype. Also, there is no readability. Once you get used to coding, `new READABLE_PART(ignore everything in here)` becomes very scannable and self documenting, as opposed to `{read: "ignore", everything: "ignore", in: "ignore", here: "ignore"} // and then come up with READABLE_PART` in your head. The first version is readable while paging up rapidly. The second, you refactor into structs merely to understand. – Chris Apr 04 '18 at 15:28
  • @nickf, if you are worried about the milliseconds spent typing 'id' and 'speaker', and would rather spew out 13 lines of utter nonsense (as in the "accepted" non-answer) to replace the simplicity of a struct, then maybe programming isn't the field for you. Then again, I could be wrong on that. And in my (not so humble) opinion your priorities might be part of the reason why the web is so horribly slow and impossible to maintain. Nothing against you as a person, but please tell me over the past 14 years you've grown to understand what I'm saying from a purely technical standpoint... – SO_fix_the_vote_sorting_bug Mar 05 '23 at 04:48
  • Thanks for your concern, mate! I've been doing fine over the last 14 years and have a very healthy career in software engineering. – nickf Aug 11 '23 at 12:30
27

The real problem is that structures in a language are supposed to be value types not reference types. The proposed answers suggest using objects (which are reference types) in place of structures. While this can serve its purpose, it sidesteps the point that a programmer would actual want the benefits of using value types (like a primitive) in lieu of reference type. Value types, for one, shouldn't cause memory leaks.

EDIT: There is a proposal in the works to cover this purpose.

//today
var obj = {fname: "Kris", lname: "Kringle"}; //vanilla object
var gifts = ["truck", "doll", "slime"]; //vanilla array

//records and tuples - not possible today
var obj = #{fname: "Buddy", lname: "Hobbs"};
var skills = #["phone calls", "basketball", "gum recycling"];

Mario
  • 6,572
  • 3
  • 42
  • 74
9

I think creating a class to simulate C-like structs, like you've been doing, is the best way.

It's a great way to group related data and simplifies passing parameters to functions. I'd also argue that a JavaScript class is more like a C++ struct than a C++ class, considering the added effort needed to simulate real object oriented features.

I've found that trying to make JavaScript more like another language gets complicated fast, but I fully support using JavaScript classes as functionless structs.

peter
  • 6,067
  • 2
  • 33
  • 44
  • 2
    I'd love to have something like structs, tuples - something that allows strongly typed collections of data - that is dealt with at compiletime and doesn't have the overhead of hashmaps like objects – derekdreery Mar 23 '16 at 09:20
  • 1
    @derekdreery Agreed. Right now you can use bit arrays but it's a huge hassle so it's only for performance optimisations. – EnderShadow8 May 09 '21 at 06:58
8

Following Markus's answer, in newer versions of JS (ES6 I think) you can create a 'struct' factory more simply using Arrow Functions and Rest Parameter like so:

const Struct = (...keys) => ((...v) => keys.reduce((o, k, i) => {o[k] = v[i]; return o} , {}))
const Item = Struct('id', 'speaker', 'country')
var myItems = [
    Item(1, 'john', 'au'),
    Item(2, 'mary', 'us')
];

console.log(myItems);
console.log(myItems[0].id);
console.log(myItems[0].speaker);
console.log(myItems[0].country);

The result of running this is:

[ { id: 1, speaker: 'john', country: 'au' },
  { id: 2, speaker: 'mary', country: 'us' } ]
1
john
au

You can make it look similar to Python's namedtuple:

const NamedStruct = (name, ...keys) => ((...v) => keys.reduce((o, k, i) => {o[k] = v[i]; return o} , {_name: name}))
const Item = NamedStruct('Item', 'id', 'speaker', 'country')
var myItems = [
    Item(1, 'john', 'au'),
    Item(2, 'mary', 'us')
];

console.log(myItems);
console.log(myItems[0].id);
console.log(myItems[0].speaker);
console.log(myItems[0].country);

And the results:

[ { _name: 'Item', id: 1, speaker: 'john', country: 'au' },
  { _name: 'Item', id: 2, speaker: 'mary', country: 'us' } ]
1
john
au
typoerrpr
  • 1,626
  • 21
  • 18
  • 1
    It looks like the struct in C/C++ and other languages, but actually it is not - the , properties in objects are not guaranteed to be ordered described as following: Definition of an Object from ECMAScript Third Edition (pdf): 4.3.3 Object An object is a member of the type Object. It is an unordered collection of properties each of which contains a primitive value, object, or function. A function stored in a property of an object is called a method – tibetty Jan 07 '19 at 03:15
3

I use objects JSON style for dumb structs (no member functions).

Robert Gould
  • 68,773
  • 61
  • 187
  • 272
2

It's more work to set up, but if maintainability beats one-time effort then this may be your case.

/**
 * @class
 */
class Reference {

    /**
     * @constructs Reference
     * @param {Object} p The properties.
     * @param {String} p.class The class name.
     * @param {String} p.field The field name.
     */
    constructor(p={}) {
        this.class = p.class;
        this.field = p.field;
    }
}

Advantages:

  • not bound to argument order
  • easily extendable
  • type script support:

enter image description here

Manuel
  • 14,274
  • 6
  • 57
  • 130
1

I made a small library to define struct if you work with ES6 compatibility.

It is a JKT parser you may checkout the project repository here JKT Parser

For an example you may create your struct like this

const Person = jkt`
    name: String
    age: Number
`

const someVar = Person({ name: "Aditya", age: "26" })

someVar.name // print "Aditya"
someVar.age // print 26 (integer)

someVar.toJSON() // produce json object with defined schema 
Aditya Kresna Permana
  • 11,869
  • 8
  • 42
  • 48
0

This is an old problem that it doesn't seem has been addressed yet. For what it's worth, I use immutability to get similar behavior. Using Typescript:

export class Point {
   public readonly X: number;
   public readonly Y: number;

   constructor(x: number, y: number)
   {
       this.X = x;
       this.Y = y;
   }

   public static SetX(value: number) : Point {
       return new Point(value, this.Y);
   }

   public static SetY(value: number) : Point {
       return new Point(this.X, value);
   }
}

This gets you a key benefit of a complex value type, namely that you can't accidentally modify the object via a reference to it.

The drawback of course is that if you DO want to modify a member you have to make a new instance, hence the static SetX and SetY functions.

It's a lot of syntactic sugar but I think it's worth it for special cases, like Point, that could potentially get used A LOT and lead to A LOT of bugs if values are changed accidentally.

Emperor Eto
  • 2,456
  • 2
  • 18
  • 32
-1

This process just worked for me. Deployed Smart Contracts from Hardhat Dev-env to Ganachi Blockchain Test Net.

File: ./scripts/deploy.js

var structJS = { id: 55, title: "GP", iccid: "56", msisdn: "1712844177", imsi: "12345" };
const USIMContract = await ethers.getContractFactory("USIM");
const usimContract = await USIMContract.deploy(89, structJS);
console.log("USIM deployed to: ", usimContract.address);

Solidity Script:

struct SolStruct { uint id; string title; string iccid; string msisdn; string imsi; }
contract USIM { 
 uint private _iccid; 
 SolStruct private _communicationProfile; 
 constructor( uint iccid_, SolStruct memory communicationProfile_ ) 
 { 
   _iccid = iccid_; 
   _communicationProfile = communicationProfile_; 
 }
}