Advanced Replacement Recall in the previous chapter how we created classes for football players, where each instance of a class was a specific player. Each had properties like firstName, lastName, and points that held useful metadata about the player. We also defined a custom toString method, taking advantage of the fact that JavaScript uses a method by that name whenever it needs to coerce an object into a string. Our toString method returned the first and last names of the player: var qb = new Quarterback("Andrew", "Dupont"); qb; //-> [Object]; qb + " just won the Heisman trophy."; //-> "Andrew Dupont just won the Heisman trophy." Because concatenating (+) a string to another object involves automatic string coercion, our instance of Quarterback knew how to represent itself in that context. Template and String#interpolate do something similar. If the object you’re inter- polating with has a special method called toTemplateReplacements, then the result of that method will be used to fill in the template string: var blatantLie = new Template( "#{position} #{firstName} #{lastName} just won the Heisman trophy."); blatantLie.evaluate(qb); //-> " Andrew Dupont just won the Heisman trophy." // adding an instance method to the Quarterback class Class.extend(Quarterback, { toTemplateReplacements: function() { return { position: "QB", firstName: this.firstName, lastName: this.lastName }; } }); blatantLie.evaluate(qb); //-> "QB Andrew Dupont just won the Heisman trophy." Here, we’ve done a before-and-after comparison. The first time we call Template#evaluate, the Quarterback instance itself is used—its own properties filling CHAPTER 8 ■ OTHER HELPFUL THINGS: USEFUL METHODS ON BUILT-INS178 in the holes of the template string. That instance has firstName and lastName properties, but it doesn’t have a position property, so an empty string is used instead. When we add Quarterback#toTemplateReplacements, though, we’re supplying a substi- tute object to use for interpolation. So we’re able to specify properties above and beyond those that already exist on the object. In this case, we’re defining a position property to go into the evaluated string. The larger purpose of defining toTemplateReplacements is the same as the purpose as defining toString: it allows you to specify in one place how your object will behave in a given context. For user-defined classes, it also promotes encapsulation, one of those hallowed OOP virtues. It’s all part of the theme that was established in Chapter 3: ensuring that objects come with instructions for use. Just like we rely on objects that mix in Enumerable to know how to enumerate themselves, here we’re relying on objects to know how to represent themselves in a Template context. Bringing It Back to String#gsub The versatility of JavaScript objects gives us a bonus way to use Template. Remember that an array is really just an object with numerals as keys, so Template and String#interpolate can be used with arrays as well: var sample = new Template("The quick brown #{0} jumps over the lazy #{1}"); sample.evaluate(["fox", "dog"]); //-> "The quick brown fox jumps over the lazy dog." Items of an array respond to property lookups just like any other object. Finally, then, I can share an Easter egg of sorts—template strings like these can be used in String#gsub: var warning = "Never, never pour salt in your eyes." warning.gsub(/(salt) in your (eyes)/, "#{2} in your #{1}"); //-> "Never, never pour eyes in your salt." You may also recall that the first item in a match array is the entire string that matches the pattern; any subsequent items are substrings that match specific captures in the regular expression. The preceding example, then, is shorthand for either of these: CHAPTER 8 ■ OTHER HELPFUL THINGS: USEFUL METHODS ON BUILT-INS 179 warning.gsub(/(salt) in your (eyes)/, function(match) { return match[2] + " in your " + match[1]; }); // or warning.gsub(/(salt) in your (eyes)/, function(match) { return "#{2} in your #{1}".interpolate(match); }); OK, I take it back: maybe strings are more awesome than we realized. Using JSON Strings are the building blocks of high-level protocols like HTTP. A client and a server communicate in plain text, which, while much less friendly than the rendered view of a browser, is nonetheless human-readable. Each time your browser requests an HTML file, it receives one gigantic string, which it then converts into a tree of objects for rendering. It converts between the two accord- ing to the rules of HTML. It can be said, then, that HTML is a serializer: it takes something inherently nonlinear and makes it linear for storage purposes. The interesting stuff is done at higher levels, with more complex data types. But on the Web, sooner or later, it all ends up as a string. JSON (JavaScript Object Notation) is simply a way to represent these complex structures as strings. What Does JSON Look Like? Prototype likes to leverage the literal syntax for object creation, so code like this should be nothing new to you: var vitals = { name: "Andrew Dupont", cities: ["Austin", "New Orleans"], age: 25 }; We can describe data structures in JavaScript with a minimum of syntactic cruft. By comparison, let’s see what this data would look like in XML: CHAPTER 8 ■ OTHER HELPFUL THINGS: USEFUL METHODS ON BUILT-INS180 <vitals> <name>Andrew Dupont</name> <cities> <city>Austin</city> <city>New Orleans</city> </city> <age>25</age> </vitals> XML is verbose by design. What if we don’t need its extra features? That’s where JSON comes in. Why JSON? There are a million different ways to exchange data between client and server; what’s so special about JSON? Very little, really. It’s not magical; it’s simply the right tool for the job in JavaScript- heavy web apps. There are JSON libraries for all the common server-side languages: PHP, Ruby, Python, Java, and many others. It’s far simpler than XML, and thus far more useful when you don’t need all of XML’s bells and whistles. We’ll revisit JSON in Part 2 of this book when we look into advanced topics in Ajax. But let’s familiarize ourselves with the basics right now. Serializing with Object.toJSON The best way to learn about the structure of JSON is to try it out yourself. Let’s create a few different JavaScript data types and see how they look in JSON: var str = "The Gettysburg Address"; var num = 1863; var arr = ["dedicate", "consecrate", "hallow"]; var obj = { name: "Abraham Lincoln", location: "Gettysburg, PA", length: 269 }; CHAPTER 8 ■ OTHER HELPFUL THINGS: USEFUL METHODS ON BUILT-INS 181 Object.toJSON(str); //-> '"The Gettysburg Address"' Object.toJSON(num); //-> '1863 Object.toJSON(arr); //-> '["dedicate", "consecrate", "hallow"]' Object.toJSON(obj); //> '{ "name": "Abraham Lincoln", "location": "Gettysburg, PA", "length": 269 }' These examples teach you several new things: • Object.toJSON will convert anything to JSON, regardless of type. • Object.toJSON always returns a string. • The way items are represented in JSON is identical to how they’re represented in JavaScript. JSON conforms to the syntax and grammar of JavaScript. In other words, in each of these cases, the string representation of the data matches the keyboard characters you’d type to describe them yourself. JSON can serialize any JavaScript data type except for functions and regular expres- sions. The Date type gets half credit: there’s no literal notation for dates, so they’re converted to a string representation. Unserializing with String#evalJSON The ease of dealing with JSON in JavaScript should be obvious: since it’s valid code, it can simply be evaluated. JavaScript includes an eval function that will evaluate a string as though it were code: var data = eval('{ "name": "Abraham Lincoln", "location": "Gettysburg, PA", "length": 269 }'); //-> [Object] But there’s a problem. It’s always dangerous to evaluate arbitrary code unless you know exactly where it’s coming from. Prototype’s String#evalJSON will evaluate a string as code, but it takes an optional Boolean; if true, it will make sure the given string is valid JSON—and therefore not a security risk. CHAPTER 8 ■ OTHER HELPFUL THINGS: USEFUL METHODS ON BUILT-INS182 var str = '{ "name": "Abraham Lincoln", "location": "Gettysburg, PA", "length": 269 }'; str.evalJSON(); //-> [Object] // Ensuring it's valid JSON str.evalJSON(true); //-> [Object] // Now try it with invalid, malicious JSON str = '{ "name": "Abraham Lincoln" }; doSomethingMalicious();'.evalJSON(true); //-> SyntaxError: Badly formatted JSON string Most of the time, you’ll be using JSON simply as a way to communicate with your own server, so security won’t be an issue. But if you happen to be handling JSON from a third party, you must make sure it’s safe to use. Overriding the Default Serialization Object.toJSON will produce a generic serialization for any object—but, as with toString and toTemplateReplacements, you can override this default. For instance, we can give our Player class (and all its subclasses) instructions on how to serialize themselves— reporting some properties and ignoring all others: // Just first name, last name, and points Class.extend(Player, { toJSON: function() { return Object.toJSON({ firstName: this.firstName, lastName: this.lastName. points: this.points }); } }); // But maybe subclasses should also report their position Class.extend(Quarterback, { toJSON: function() { return Object.toJSON({ position: 'QB', firstName: this.firstName, lastName: this.lastName, points: this.points CHAPTER 8 ■ OTHER HELPFUL THINGS: USEFUL METHODS ON BUILT-INS 183 . JavaScript objects gives us a bonus way to use Template. Remember that an array is really just an object with numerals as keys, so Template and String#interpolate can be used with arrays as well: var. PHP, Ruby, Python, Java, and many others. It’s far simpler than XML, and thus far more useful when you don’t need all of XML’s bells and whistles. We’ll revisit JSON in Part 2 of this book when. Quarterback("Andrew", "Dupont"); qb; //-> [Object]; qb + " just won the Heisman trophy."; //-> "Andrew Dupont just won the Heisman trophy." Because concatenating