0

I'm not sure if I understand coupling correctly. I've been told that if I move my data out into an external data file (like JSON), it reduces coupling overall. (I agree with this.)

However, if I were to create an object to hold the data contained with the JSON, whose fields map directly to the JSON's structure, wouldn't that still create a tightly coupled link between the object and JSON?

For example:

"images": [{
    "source" : "images/foo.png",
    "size" : "thumbnail",
    "alt" : "Foo"
},
{
    "source" : "images/bar.png",
    "size" : "thumbnail",
    "alt" : "bar"
}]

And then we have some object, part of the application's model:

function FooBarImage (jsonObj) {
    this.source = jsonObj.source;
    this.size = jsonObj.size;
    this.alt = jsonObj.alt;
}

FooBarImage.prototype.doStuff = function () { ... }

Here, the FooBarImage object knows about the internal format of the JSON objects. If the format of the JSON data were to change (e.g. we want to add a new field, or rename an existing one), wouldn't we have to also make changes to the constructor function?

Am I misunderstanding something, or is there another way that decouples the code and the data even further?

user341554
  • 569
  • 2
  • 8
  • 16
  • The entire point of JSON is to transport the object. There would be no need to create a second object that maps to the JSON object. – Scott Marcus May 26 '17 at 12:54
  • 1
    [There's no such thing as a "JSON Object"](http://benalman.com/news/2010/03/theres-no-such-thing-as-a-json/) – Andreas May 26 '17 at 12:55
  • 1
    @Andreas While you are correct, the term "JSON Object" has become rather ubiquitous and it is obvious what the OP is trying to say. While posts like the one you link to are informative, your comment could be interpreted as splitting hairs. – Scott Marcus May 26 '17 at 12:58
  • @ScottMarcus To be more precise, it's really more of a mapping of the JSON data to another object with the same fields and a few methods. As I understand, JSON is meant to be a pure data container, so I don't think I should have methods within the JSON data itself, hence the need for another object. – user341554 May 26 '17 at 13:07
  • @Andreas I'm aware of that; I simply chose the name `jsonObj` for the parameter to make it clear what kind of data was being passed. In reality, I'd probably just use `data` or something else for the name. – user341554 May 26 '17 at 13:09
  • @user341554 But, there's still no reason to map to another object. Just take the JSON string you are receiving and use `JSON.parse()` to turn it into a true JavaScript object. At that point JSON is out of the picture, you can then add whatever new methods you need to that object. You don't need a second object. – Scott Marcus May 26 '17 at 13:28

1 Answers1

0

You could easily "reflect"/"map" your jsonObject (the literal object resulting from the deserialization of the provided JSON string). You can loop through the object properties and assign them to your instance:

function FooBarImage (jsonObj) {
    Object.keys(jsonObj).forEach(function(prop) {
        this[prop] = jsonObj[prop];
    }
}

FooBarImage.prototype.doStuff = function () { ... }

Sample:

function dirtyMap(fromObj, toObj) {
 Object.keys(fromObj).forEach(function(prop) {
  toObj[prop] = fromObj[prop];
 });
 return toObj; // chain me?
};

function FooBarImage (jsonObj) {
    dirtyMap(jsonObj, this);
};
FooBarImage.prototype.doStuff = function () { 
 console.log(this);
};

var json = `[{
    "source" : "images/foo.png",
    "size" : "thumbnail",
    "alt" : "Foo"
},
{
    "source" : "images/bar.png",
    "size" : "thumbnail",
    "alt" : "bar"
}]`
  , images = JSON.parse(json)
  , instances = images.map(function(img) { return new FooBarImage(img); })
  ;

instances.forEach(function(instance) { instance.doStuff(); });

An other solution (along with others of course), would be to use a factory:

var createFooBarImage = function createFooBarImage(img) {
    var fooBarImage = Object.create(img);
    fooBarImage.doStuff = function () { 
        console.log(this);
    };
    return fooBarImage;
}

Sample:

var json = `[{
 "source" : "images/foo.png",
 "size" : "thumbnail",
 "alt" : "Foo"
},
{
 "source" : "images/bar.png",
 "size" : "thumbnail",
 "alt" : "bar"
}]`
  , createFooBarImage = function createFooBarImage(img) {
  var fooBarImage = Object.create(img);
  fooBarImage.doStuff = function () { 
   console.log(this);
  };
  return fooBarImage;
 }
  , images = JSON.parse(json)
  , instances = images.map(function(img) { return createFooBarImage(img); })
  ;

instances.forEach(function(instance) { instance.doStuff(); });
Booster2ooo
  • 1,373
  • 9
  • 11
  • Will this handle nested objects/arrays? Wouldn't `Object.create()` do the trick here? And, I have to again ask, why do this when you can just add whatever you need to the object that was parsed from `JSON.parse()`? – Scott Marcus May 26 '17 at 13:59
  • @ScottMarcus indeed, it wont, it's just a sample in order to illustrate the concept. I added an example using a 'kind of' factory pattern. But still, there are other way to map/extend objects. – Booster2ooo May 26 '17 at 14:12
  • Why not simply `var newObj = Object.create(sourceObj)`? – Scott Marcus May 26 '17 at 14:13
  • @ScottMarcus you can but you still don't have an instance of `FoorBarImg`, newObj doesn't have a doStuff method does it? – Booster2ooo May 26 '17 at 14:15
  • No, but that's the point. Use `Object.create()` instead of your cloning code and then just add methods to the prototype of the returned object and done. – Scott Marcus May 26 '17 at 14:18
  • @ScottMarcus I edited the samples. Still, it depends a bit on the actual code. If he already developped everything using prototypes, it's gonna be a pain to refactor I guess. – Booster2ooo May 26 '17 at 14:34
  • Maybe I'm missing something, but I think you are still making this much more complex than it needs to be. See this: https://jsfiddle.net/g5jdxhuz/5/ and tell me what I've missed. – Scott Marcus May 26 '17 at 14:46
  • @ScottMarcus The aim is to create instances of FooBarImage based on each literal object in the array so each resulting object have the method `doStuff`. In your example `newImages[0]` doesn't have the so called `doStuff` method. – Booster2ooo May 26 '17 at 14:59
  • And, how does this not solve the problem much more simply: https://jsfiddle.net/g5jdxhuz/7/ – Scott Marcus May 26 '17 at 15:05
  • @ScottMarcus that's the idea. But now, imagine that `doStuff` (`doSomething`) is already part of a full prototype avec 10s/100s of other methods, how do you apply them all ? – Booster2ooo May 26 '17 at 15:42
  • But, that's not part of the stated problem domain. I think what the OP is asking is much less complex than this and can be handled without any new objects being created. – Scott Marcus May 26 '17 at 15:45