Category Archives: Javascript

unit-testing angularJS controllers in a non-trivial environment

I just released a new patch version of my angularJS sekeleton project ngStart. It now contains a sample unit test for a controller.

There are many examples out there how to unit test an angularJS controller. Basically you have to create a new controller scope yourself and tell angularJS to create a new scope instance for a given controller. This looks usually like this (as a jasmine test):

describe("the controller", function () {
	var contactController, scope;
	beforeEach(function () {
		inject(function ($rootScope, $controller) {
			scope = $rootScope.$new();
			contactController = $controller("contactController", {$scope: scope});
		});
	});

	it("should be something", function () {
		expect(scope.someProperty).toBeDefined();
	});
});

Note: this code assumes that the controller was registered in the production code via a call to angular.controller like this: angular.controller(“contactController”, function($scope){…}

The catches in my ngStart project are, that

  • its a requireJS environment and
  • that the controller is not registered by name as its usually done in simple projects, but the controller is defined inside a route definition without an explicit name. The controller is only valid for the given route. As far as I know, you can’t access it via a name anywhere outside.

The route definition looks like this:

define(['angular', 'ContactController'], function (angular, ContactController) {

	var contact = angular.module("contact", []);

	contact.config(["$routeProvider", function($routeProvider) {
		$routeProvider.when('/contact/', {
			templateUrl: 'contact.html',
			controller: ContactController
		});
	}]);

	return contact;
});

 

How do you test the controller now in a unit test if you can’t access it via name?

Easy.

As we can load the controller definition with requireJS (aka its function representation) you can give the $controller function this controller function directly. You don’t have to provide a string which angularJS uses to look for a registered function like its done with a call to angular.controller(“name”, controllerFunction).

The rest is the standard behaviour like above:

define(["contact/ContactController"], function(ContactController) {
	describe("the controller", function () {
		var contactController, scope;

		beforeEach(function () {
			inject(function ($rootScope, $controller) {
				scope = $rootScope.$new();
				contactController = $controller(ContactController, {$scope: scope});
			});
		});

		it("should be something", function () {
			expect(scope.someProperty).toBeDefined();
		});
	});
});

Frontend optimizations for HTML5 web apps

I did a presentation in front of some colleagues some time ago and it was then when I realized how successful our frontend optimizations really are.

Our mobile web app which is running with angularJS and jQuery Mobile is loading over 1.6MB in more than 50 HTTP requests in local development mode. Only the laziest developers would give THAT to their customers.

After all optimizations done during grunt build, customers need to load only 250kB in 13 HTTP requests on their first visit. Although the number of HTTP seems a bit high, this is a appcache app and it contains all ressources ever needed for the app, including HTML files and all images, fonts and styles.

Because of appcache caching a returning visitor needs to only load 3kB for the whole app in 1 HTTP request. Thats, right. 3 kB! Thats the initial payload for the JSON data that the app loads from a REST service.

3kB instead of 1.6MB. How is that possible?

Here is a list of most optimzations that is done during grunt build:

  • all javascript files are concatenated and optimized with requireJS optimizer into one single javscript file. This single steps is gaining by far the most in size and HTP requests as we structure our code in small and meaningful modules/files. Side effect: optimizer creates javascript source maps so debugging is possible even in minized code on production.
  • all css files are concatenated and optimized into one single css file
  • all images (icons) are written as base64 into the css file as data URIs. Though the images are around 20% bigger, the browser saves heavily on fewer HTTP request which outweighs the bigger data by far!
  • an appcache manifest file is created during build and referenced inside the HTML files. The browser can then load the whole app during first visit and needs only to load dynamic data on subsequent visits.

That is only a part of the build process. What I learned during developing this build system, was the foundation what I did later in my angularJS skeleton project: ngStart

Grunt is a fabulous build tool and there is already a plugin for most, if not all frontend optimizations. You only have to configure them.

AddToHomescreen: pin web apps to the homescreen

add2home-screen1Apples iOS supports since ages a meta tag called “apple-mobile-web-app-capable”, with which a developer can tell iOS that web site doesn’t need navigation and url bars and is handling that itself. So, if this meta tag is set and a web site is called from the iOS homescreen it looks like a normal app for the normal visitor.

The problem is to tell its visitors that this behaviour is supported as most users don’t know that there is even the possibility to add websites to their homescreens.

But like they say – there is an app for that – there is a library for that: Matteo Spinelli has developed the wonderful tool AddToHomescreen. If you embed the code inside your web app, a small tooltip will be visible after page load which asks the user to add the site to their homescreen. The library checks itself that its only visible on compatible iOS devices and changes the little icon what the user should tap on automatically (its different on iPads and iPhones). Additionally you can configure some options like if and how long the tooltip stays or how long the tooltip remembers that the user has closed the tooltip and will not see it again.

After optimizing and deleting not-needed languages its payload is only 6 kB.

Attention if you are using requireJS

AddToHomeScreen uses the onload event to init itself by default, but that event is already gone when loading it via requireJS … at least when using it together with r.js optimizer (which you should!). But there is a (advanced) config option for this:

window.addToHomeConfig = {hookOnload: false}

If this option is set, the library inits itself as soon as its loaded and doesn’t wait for the (already gone) load event.

Appcache Manifest erstellen und in einer angularJS App integrieren

Wer eine ausgewachsene App entwickelt, die aus rein statischen Dateien (*js, *.css, *.html) besteht und die dynamischen Daten ausschließlich über einen REST-Server nachlädt, sollte sich in jedem Fall mal mit dem HTML5 Appcache beschäftigen. In so einem Fall kann man die (z.B. bei alistapart etwas übertriebenen beschriebenen) Nachteile nämlich ruhig ignorieren.

Der Vorteil für den Nutzer die kompletten statischen Daten immer sofort auf dem Gerät parat zu haben und außer dem REST-Request überhaupt keine HTTP-Requests zum Laden der App zu benötigen, ist speziell für mobile Anwendungen ein ungeheurer Performancegewinn.

Wie ein Appcache-Manifest aufgebaut ist, wird z.B. auf HTML5 Rocks beschrieben.

Nun will man die Datei aber natürlich möglichst automatisch erstellen. Wir haben uns – da wir grunt als Buildtool verwenden – für grunt-contrib-manifest entschieden. Für mich ein weiterer Grund sehr zufrieden mit unserer Entscheidung zu sein, grunt zu verwenden. Es gibt für praktisch alle Anforderungen im Bereich Webentwicklung bzw. Frontend-Entwicklung bereits ein Plugin. [highlight1]”Da gibts ein Plugin für”[/highlight1] statt “Da gibt’s eine App für” sozusagen 🙂

Die Konfiguration ist simpel und selbsterklärend, da muss ich nicht weiter drauf eingehen. Und funktioniert bisher problemlos.

Was etwas mehr nachdenken erfordert hat, war, wie man auf das Event “updateready” reagieren soll, das der Browser erzeugt, wenn er die Dateien im Appcache vollständig aktualisiert hat und die neue Version der App geladen werden muss. Dass eine nicht wegklickbare Info-Box erscheinen sollte, die der Nutzer nur mit einem Klick auf “Neu laden” schließen können sollte, war recht schnell klar. Das eigentliche “Problem” war, das solche DOM-Manipulationen nicht in einen angularJS Controller gehören. Schließlich sollen die auf jeden Fall auch ohne DOM unittest-bar bleiben.

Also war eine Direktive notwendig.

Wir haben es momentan mit dieser Direktive gelöst. Das Popup benutzt jQuery Mobile.

function AppcacheUpdated() {
	var elementScope;

	window.applicationCache.addEventListener('updateready', function() {
		if (window.applicationCache.status === window.applicationCache.UPDATEREADY) {
			window.applicationCache.swapCache();
			elementScope.show = true;
			if (!elementScope.$$phase) {
				elementScope.apply(elementScope.show);
			}
		}
	});

	return {
		restrict:'E',
		template:" <div data-role='popup' class='appcachePopup ui-content' data-theme='e'>" +
			" <p><translate>Neue Version</translate></p><br />" +
			"<a ng-click='reload()' data-role='button'>Neu laden</a> </div>",
		scope:{
		},
		controller: function ($scope){
			$scope.$watch("show", function(value) {
				if (value) {
					$(".appcachePopup").popup("open", {dismissible:false});
				}
			});

			$scope.reload = function() {
				window.location.reload();
			};

			$scope.show = false;
		},
		link: function (scope) {
			elementScope = scope;
		}
	};
}

angularModule.directive("appcacheUpdated", AppcacheUpdated);

Die Direktive zu verwenden ist simpel, einfach in jede HTML-Seite, auf der sie verwendet werden soll, irgendwo einbauen:

<appcache-updated></appcache-updated>

Das ist so ziemlich meine erste Direktive. Mal schauen ob sie auch weiterhin so funktionieren wird, wie sie es momentan tut. Oder ob es dort noch Fallen gibt, über die ich bisher nicht gestolpert bin. Vor allem bin ich mir momentan noch etwas unsicher, ob es günstig ist, sich den “element.$scope” in einer Closure zu merken.

Die Komplexität um angularJS-Direktiven zu schreiben, ist aber in jedem Fall deutlich geringer als JSF-Komponenten zu schreiben. Ich bin immer mehr der Überzeugung, dass JSF für mich keine Zukunft mehr hat, wenn es um Frontend-Entwicklung geht. Der Ansatz auf dem Server das HTML zu rendern und den Client darstellen zu lassen, hat sich in den letzten Jahren einfach überlebt.

Die Entwicklung mit angularJS ist erstens deutlich einfacher und zweitens … macht es einfach Spass. 🙂

Javascript in der IDE debuggen mit Webstorm

Gleich vorweg, das Video ist nicht von mir und hat auch keinen Ton.

Nichtsdestotrotz sieht man dort sehr schön wie man mit der aktuellen Testacular Version in der aktuellen Webstorm Version Javacscript direkt in der besten Javascript IDE debuggen kann, die es momentan gibt: Webstorm.

Das ist nicht übertrieben. Die Jungs von Jetbrains sind momentan absolut führend in der Integration von Javascript in einer IDE. Wer keinen reinen Editor wie Sublime verwenden möchte sondern eine ausgewachsene sinnvoll vorkonfigurierte IDE, dem bleibt nur Webstorm.

Trotz des Videos hier nochmal die wichtigsten Konfigurationsfenster zusammengefasst. Es wird eine laufende Testacular Installation vorausgesetzt, wie das z.B. geht habe ich z.B. in meinem Artikel erklärt.

Die gesamte Konfiguration ist in 2-3 Schritten erledigt und baut auf die “Run/Debug Configurations” auf. Die sind über die Icon-Leiste erreichbar (siehe Bild).

Dort kann man zwei oder drei Konfigurationen anlegen. Drei benötigt man, wenn die Tests nicht automatisch nach jedem Speichern laufen sollen, sondern auf Knopfdruck.

Die erste Konfiguration ist der Server (siehe Bild). In dem Menü muss man über das + eine neue node.js Konfiguration anlegen. Die dort einzutragenden Werte seht ihr auf dem Bild. Die Pfade sind entsprechend anzupassen, auf dem Bild sind die Pfade für einen Mac zu sehen.

Die zweite (optional) anzulegende Konfiguration, ist die manuelle Ausführung der Tests, wenn der Server nicht automatisch loslegen soll (abhängig von der Konfiguration in der testacular.conf.js). Auch hier muss wieder eine node.js Konfiguration angelegt werden und als Startparameter für Testacular gibt man diesmal nur “run” an. Das wars schon.

Jetzt kann man bereits die Tests ausführen und man sieht die Ergebnisse in der Konsole. Wenn Fehler aufgetreten sind, sieht man dort die Dateien, die alle anklickbar sind sodass man direkt an der betreffenden Codestelle landet.

Wenn man nun noch debuggen möchte, fügt man wieder über das + und “Javascript Debug” eine “Remote” Konfiguration hinzu. Die genaue Konfiguration ist wieder auf dem Bild sichtbar. Spannend ist hier nur die Wahl des Browsers (aktuell Chrome oder Firefox) und die Remote URL. Im Fall von Testacular und dem Standardport ist das [highlight1]http://localhost:9876/base[/highlight1]

Das wars. Man kann den vorher angelegten Server nun nicht mehr nur starten, sondern über das Debug-Icon auch debuggen. Das startet den gewählten Browser und stellt eine Verbindung her. Das debugging sollte direkt funktionieren, das genaue Vorgehen sieht man in obigem Video.

[fancy_images]
[image title=”Run/Debug Configurations” caption=”Run/Debug Configurations”]http://entwicklertagebuch.com/blog/wp-content/uploads/2013/01/editconfigurations.png[/image]
[image title=”Testacular Server starten” caption=”Testacular Server starten”]http://entwicklertagebuch.com/blog/wp-content/uploads/2013/01/server.png[/image]
[image title=”Testacular Tests starten” caption=”Testacular Tests starten”]http://entwicklertagebuch.com/blog/wp-content/uploads/2013/01/run.png[/image]
[image title=”Testacular Debug Konfiguration” caption=”Testacular Debug Konfiguration”]http://entwicklertagebuch.com/blog/wp-content/uploads/2013/01/debug.png[/image]
[image title=”Testacular Server gestartet” caption=”Testacular Server gestartet”]http://entwicklertagebuch.com/blog/wp-content/uploads/2013/01/serverstarted.png[/image]
[/fancy_images]

angularJS App mit requireJS und Testacular testen

Wie schon ein paar mal geschrieben, bin ich schwer begeistert wie einfach es mit angularJS ist, Web Apps mit Javascript zu entwickeln. Vielleicht merkt man das auch an der ein oder anderen Stelle 🙂

Aber egal, wie einfach das Entwickeln ist, wir machen alle Fehler. Deshalb muss auch angularJS-Code getestet werden, mindestens Unit-Tests besser noch mit End-2-End-Tests.

[highlight1]Aber moment mal, Javascript-Code und Unit-Tests? Geht das überhaupt? [/highlight1]

Aber klar! Schon lang gibt es diverse Test-Frameworks für Javascript. jQuery benutzt z.B. qunit, die AngularJS-Entwickler bevorzugen Jasmine.

Als Skriptsprache, die beim Kunden im Browser ausgeführt wird, ist es bei Javascript-Tests natürlich wichtig, sie dort auszuführen wo sie laufen: im Browser. Und das ist ohne spezialisierte Tools äußerst aufwändig.

Als sogenannter Test Runner, gibt es seit einigen Jahren jsTestDriver (von Google), das Unit-Tests entgegennimmt und dafür sorgt, sie auf angeschlossenen Browsern auszuführen. Leider habe ich keine gute Erfahrung damit gemacht, da es allgemein recht instabil läuft, mehrfach täglich neugestartet werden muss und nach Neustart das automatische Wiederverbinden mit den Browsern eher hakelig ist. Außerdem scheint die Weiterentwicklung auch eher eingeschlafen zu sein.

Die AngularJS-Entwickler haben sich deshalb entschlossen, einen eigenen Test Runner zu entwickeln. [highlight1]Testacular[/highlight1].

Testacular ist kein Testframework, sondern wirklich nur der Test Runner. Man schreibt die Unit-Tests also z.B. als Jasmine-Tests und führt sie mit Testacular in Chrome oder Firefox oder Safari oder auch in PhantomJS (Headless Browser) aus.

Da wir AngularJS u.a. auch mit requireJS benutzen, ist es allerdings nicht ganz einfach, als Anfänger zu verstehen, wie man Testacular konfigurieren muss, damit requireJS den Produktions- und Testcode asynchron nachladen kann und erst danach Testacular loslegt um die Tests auszuführen.

Standardmässig liefert Testacular nämlich eine Webseite an den Browser aus, in der alle referenzierten *.js-Dateien automatisch als <script>-Tag referenziert werden, damit der Browser sie lädt. Nach Laden der Seite fängt dann Testacular an, die Tests auszuführen. Mit requireJS darf Testacular allerdings die Skripte eben gerade nicht mittels <script>-Tag laden, sondern dies übernimmt require.js. Nur die main.js um requireJS zu laden und zu konfigurieren, darf so eingebunden werden.

Als Startpunkt für meine Konfiguration mit requireJS habe ich einen Artikel bei Jake Trent gefunden, der sich genau diesem Problem widmet. Im Gegensatz zu diesem Artikel kann ich aber sagen, dass die komplette requireJS-Konfiguration wie man sie im Produktionsbetrieb gewohnt ist, auch für Testacular funktioniert. Testacular bindet mit meiner Konfiguration nur noch Jasmine, requireJS und die main-test.js ein. Alles andere lädt requireJS nach.

Dies erreicht man mit folgender Konfiguration der Dateien:

files = [
	JASMINE,
	JASMINE_ADAPTER,
	REQUIRE,
	REQUIRE_ADAPTER,
	{pattern:'src/main/external-libs/angular-1.0.3.js', included:false},
	{pattern:'src/test/external-libs/angular-mocks-1.0.3.js', included:false},
	{pattern:'src/main/js/**/*', included:false}, //Produktionscode
	{pattern:'src/test/unit/**/*.js', included:false}, //Testcode
	'src/test/main-test.js' //requireJS-Konfiguration
];

Entscheidend ist hier das [hightlight1]included:false[/highlight], was dafür sorgt, dass die Dateien zwar verfügbar sind, aber nicht per <script> automatisch ausgeliefert werden. Der Rest der Konfiguration kann so aus dem verlinkten Artikel übernommen werden.

In der main-test.js Datei muss man nun dafür sorgen, dass die eigene Anwendung geladen wird (die ja sowieso schon aus require-Module bestehen sollte), die Testklassen geladen werden (das müssen nun auch alles require-Module sein, siehe Artikel) und wenn das alles geladen ist, muss Testacular mitgeteilt werden, dass es nun loslegen kann. Meine main-test.js sieht wie folgt aus:

(function (window, require) {
	"use strict";
	var file, requireModules;
	requireModules = [];

	//aus den von Testaular bereitgestellten Dateien alle *Test-Dateien raussuchen und Pfad anpassen, damit requireJS
	//was damit anfangen kann
	for (file in window.__testacular__.files) {
		if (window.__testacular__.files.hasOwnProperty(file)) {
			if (file.substring(file.length - 7, file.length) === 'Test.js') {
				requireModules.push("../.." + file.substring(9, file.length - 3));
			}
		}
	}

	// unsere eigene Anwendung inkl. Abhängigkeiten laden
	requireModules.push("app");

	//angular Mocks laden
	requireModules.push("mocks");

	require({
		// "/base" ist die URL von der Testacular alle Dateien ausliefert,
		// "src/main/js" ist due Base-URL aller Module die nicht Test sind
		baseUrl:'/base/src/main/js',
		paths:{
			'angular':'../external-libs/angular-1.0.3',
			'mocks':'../../test/external-libs/angular-mocks-1.0.3'
		},
		shim:{
			'angular':{ exports:'angular' },
			'mocks':{ deps:['angular'], exports:'mocks' }
		}
	}, requireModules, function () {
		//erst hier wird Testacular tatsächlich (manuell) gestartet
		window.__testacular__.start();
	}, function (err) {
		//hier kommt die Fehlerbehandlung hin
	});
}(window, require));

Über den Zugriff auf [highlight1]window.__testacular__.files[/highlight1] hole ich mir alle Testdateien und bastele daraus die korrekt benamten require-Module. Dann wird noch die eigentliche Anwendung und Angular hinzugefügt. Wenn requireJS alles geladen hat, wird dann über [highlight1]window.__testacular__.start();[/highlight1] Testacular gestartet.

Wenn man das einmal begriffen hat, ist es eigentlich recht einfach. Aber das ist ja häufig so 🙂 Testacular ohne requireJS zu konfigurieren, ist auch deutlich einfacher.

Wenn es dann einmal läuft, hat man aber einen hervorragenden Testrunner, den man dann auch in Webstorm (Javascript IDE) integrieren kann, um direkt in der IDE, die Unit Tests starten zu können und – besonders beeindruckend – auch direkt in der IDE debuggen kann.

Aber dazu ein anderes mal mehr Details.

Zentrale Lokalisierung in einer angularJS App

angularJS ist großartig und macht Spaß. Je mehr Code ich damit entwickle, umso begeisterter bin ich.

Um auch in unserer Web App einen zentralen Platz für unsere Lokalisierungen zu haben, habe ich einen Service geschrieben, der sich zentral um die Lokalisierung von allen Texten kümmern soll. Als Java-Entwickler wollte ich etwas ähnliches entwickeln, wie ResourceBundles und Properties-Dateien.

Meine Lösung ist als Angular-Service implementiert und die Lokalisierungstexte sind in JSON-Dateien ausgelagert. Der Service lädt die passende JSON-Datei asynchron per [highlight1]$http-Service[/highlight1] nach und stellt die Texte systemweit über eine [highlight1]localizationService.translate()[/highlight1]-Methode zur Verfügung. Die Lokalisierungsstrings lassen sich den Java-Properties parametrisieren, wobei die aktuelle Implementierung nur einen Parameter auswertet, weil wir aktuell nur einen benötigen. Die Formatierung greift dabei auf eine format-Methode am String-Prototyp zurück.

Hinweis: der Code um die Browsersprache zu erkennen fehlt, damit der Code einfacher zu verstehen ist.

	function localizationServiceFactory($http, $q) {
		var translationsDE, errorMessage;

		function loadTranslations() {
			var deferred = $q.defer();

			if (!translationsDE) {
				$http.get("/json/translations/translationsDE.json").then(
					function(data) {
						translationsDE = data;
						errorMessage = '';
						deferred.resolve();
					},
					function(error) {
						translationsDE = {};
						errorMessage = error;
					}
				);
			} else {
				deferred.resolve();
			}

			return deferred.promise;
		}

		function translate(key, param) {
			var translation = "";

			return loadTranslations().then(function() {
				if (key) {
					translation = translationsDE[key];

					//unbekannter Key = gib Key zurück
					if(!translation) {
						translation = key;
					}

					if (param) {
						translation = translation.format(param);
					}
				}

				return translation;
			});
		}

		return {
			translate:translate
		};
	}

	localizationServiceFactory.$inject = ['backendService', '$q'];
	angularModule.factory("localizationService", localizationServiceFactory);
{
	"MEIN_STRING": "Das ist die Lokalisierung",
	"NOCH_EIN_STRING": "Und noch eine",
	"FALSCHER_BENUTZER": "Benutzername {0} wurde nicht gefunden."
}
//format Methode, {0} {1} usw.
if (!String.prototype.hasOwnProperty("format")) {
	String.prototype.format = function() {
		var formatted, i, regexp;
		formatted = this;
		for (i = 0; i < arguments.length; i++) {
			regexp = new RegExp('\\{'+i+'\\}', 'gi');
			formatted = formatted.replace(regexp, arguments[i]);
		}
		return formatted;
	};
}

Das wirklich schöne an dem Service ist, dass er die JSON-Datei asynchron nachlädt, wenn sie gebraucht wird. Wenn man den Code zur Spracherkennung entsprechend baut, lädt er auch nur die tatsächlich benötigte Lokalisierung. Nicht benötigte Lokalisierungen landen erst gar nicht im Browser.

Was mich am meisten beeindruckt hat, ist der Umgang von AngularJS mit dem [highlight1]Promise-Objekt[/highlight1] das die translate-Methode in jedem Fall zurückgibt. In diesem Fall sorgt angularJS nämlich automatisch dafür, dass die Aufrufe von translate aus einem Controller heraus synchron aufgelöst werden, sodass man im Controller ohne besonderes Zutun auf jeden Fall einen lokalisierten String bekommt, obwohl der $http-Request asynchron abläuft und zum Zeitpunkt des Aufrufes eventuell noch gar nicht abgeschlossen ist.

Man könnte es fast als Magie bezeichnen 🙂

grunt – Ein Javascript Build Tool

Momentan beschäftige ich mich erstmals mit einer reinen Javascript Web App, die komplett aus statischen HTML- und JS-Dateien besteht und über eine REST-Schnittstelle mit (dynamischen) Daten versorgt wird. Als Technologien verwende ich dabei so interessante Sachen, wie [highlight1]angularJS[/highlight1], [highlight1]jQuery Mobile[/highlight1] und [highlight1]Jasmine[/highlight1]. Alles super spannend.

Aber wie verwaltet man so eine App im professionellen Einsatz, wenn man an Maven als Build-, Dependency Management- und Deployment-Tool gewohnt ist? Und im Jenkins (Continous Integration) soll es natürlich ebenfalls integrierbar sein. Und regelmässige Unit-Tests sollen auch ausgeführt werden. Und automatisch verteilte Builds, die ins automatische Deployment einlaufen.

Einfach mal die HTML- und JS-Dateien manuell ins Apache-Verzeichnis zu kopieren, kommt jedenfalls nicht in Frage.

Continue reading grunt – Ein Javascript Build Tool

Kommentare in JSON

Fun Fact: Es gibt keine offizielle Möglichkeit in JSON-Daten Kommentare unterzubringen.

Gerade als Entwicklerteam, dass sich erstmals mit JSON, Javascript und Client-Programmierung beschäftigt, sind Kommentare eine gute Möglichkeit sich zu erklären. Deshalb war ich überrascht, dass es tatsächlich keine Kommentare in JSON gibt.

In den ersten Versionen des Standards gab es zwar Kommentare, aber nachdem Douglas Crockford nicht gefiel, was die Leute mit Kommentaren anstellten, hat er sie ersatzlos aus dem Standard entfernt.

Nichtsdestotrotz halte ich diese Entscheidung für völlig falsch. Es ist für mich einfach völlig unverständlich warum z.B. in einer “package.json”-Konfigurationsdatei keinerlei Kommentare möglich sind, um bestimmte Einstellungen anderen Entwicklern zu erklären.

Als Konvention könnte man sich aber z.B. darauf einigen, Kommentare in der Datenstruktur selbst unterzubringen und diese Daten einfach nicht verarbeiten. Beispiel:

{
   "__comment" : "Das hier ist ein Kommentar",
   "success" : true,
   "data": {
      "title": "Es",
      "author": "Stephen King", 
      "isbn": "1234567890"
   }
}

Für Puristen sicher ein Graus, aber leider so ziemlich die einzige Möglichkeit.

Eine längere Abhandlung und auch eine Erklärung der Historie findet sich im getiblog