





















































In this article by Ray Rischpater, author of the book JavaScript JSON Cookbook, we show you how you can use strong typing in your applications with JSON using C#, Java, and TypeScript. You'll find the following recipes:
(For more resources related to this topic, see here.)
While some say that strong types are for weak minds, the truth is that strong typing in programming languages can help you avoid whole classes of errors in which you mistakenly assume that an object of one type is really of a different type. Languages such as C# and Java provide strong types for exactly this reason.
Fortunately, the JSON serializers for C# and Java support strong typing, which is especially handy once you've figured out your object representation and simply want to map JSON to instances of classes you've already defined. We use Json.NET for C# and gson for Java to convert from JSON to instances of classes you define in your application.
Finally, we take a look at TypeScript, an extension of JavaScript that provides compile-time checking of types, compiling to plain JavaScript for use with Node.js and browsers. We'll look at how to install the TypeScript compiler for Node.js, how to use TypeScript to annotate types and interfaces, and how to use a web page by Timmy Kokke to automatically generate TypeScript interfaces from JSON objects.
In this recipe, we show you how to use Newtonsoft's Json.NET to deserialize JSON to an object that's an instance of a class. We'll use Json.NET because although this works with the existing .NET JSON serializer, there are other things that I want you to know about Json.NET, which we'll discuss in the next two recipes.
To begin, you need to be sure you have a reference to Json.NET in your project. The easiest way to do this is to use NuGet; launch NuGet, search for Json.NET, and click on Install, as shown in the following screenshot:
You'll also need a reference to the Newonsoft.Json namespace in any file that needs those classes with a using directive at the top of your file:
usingNewtonsoft.Json;
Here's an example that provides the implementation of a simple class, converts a JSON string to an instance of that class, and then converts the instance back into JSON:
using System; usingNewtonsoft.Json; namespaceJSONExample { public class Record { public string call; public double lat; public double lng; } class Program { static void Main(string[] args) { String json = @"{ 'call': 'kf6gpe-9', 'lat': 21.9749, 'lng': 159.3686 }"; var result = JsonConvert.DeserializeObject<Record>( json, newJsonSerializerSettings { MissingMemberHandling = MissingMemberHandling.Error }); Console.Write(JsonConvert.SerializeObject(result)); return; } } }
In order to deserialize the JSON in a type-safe manner, we need to have a class that has the same fields as our JSON. The Record class, defined in the first few lines does this, defining fields for call, lat, and lng.
The Newtonsoft.Json namespace provides the JsonConvert class with static methods SerializeObject and DeserializeObject. DeserializeObject is a generic method, taking the type of the object that should be returned as a type argument, and as arguments the JSON to parse, and an optional argument indicating options for the JSON parsing. We pass the MissingMemberHandling property as a setting, indicating with the value of the enumeration Error that in the event that a field is missing, the parser should throw an exception. After parsing the class, we convert it again to JSON and write the resulting JSON to the console.
If you skip passing the MissingMember option or pass Ignore (the default), you can have mismatches between field names in your JSON and your class, which probably isn't what you want for type-safe conversion. You can also pass the NullValueHandling field with a value of Include or Ignore. If Include, fields with null values are included; if Ignore, fields with Null values are ignored.
The full documentation for Json.NET is at http://www.newtonsoft.com/json/help/html/Introduction.htm.
Type-safe deserialization is also possible with JSON support using the .NET serializer; the syntax is similar. For an example, see the documentation for the JavaScriptSerializer class at https://msdn.microsoft.com/en-us/library/system.web.script.serialization.javascriptserializer(v=vs.110).aspx.
Dates in JSON are problematic for people because JavaScript's dates are in milliseconds from the epoch, which are generally unreadable to people. Different JSON parsers handle this differently; Json.NET has a nice IsoDateTimeConverter that formats the date and time in ISO format, making it human-readable for debugging or parsing on platforms other than JavaScript. You can extend this method to converting any kind of formatted data in JSON attributes, too, by creating new converter objects and using the converter object to convert from one value type to another.
Simply include a new IsoDateTimeConverter object when you call JsonConvert.Serialize, like this:
string json = JsonConvert.SerializeObject(p, newIsoDateTimeConverter());
This causes the serializer to invoke the IsoDateTimeConverter instance with any instance of date and time objects, returning ISO strings like this in your JSON:
2015-07-29T08:00:00
Note that this can be parsed by Json.NET, but not JavaScript; in JavaScript, you'll want to use a function like this:
Function isoDateReviver(value) { if (typeof value === 'string') { var a = /^(d{4})-(d{2})-(d{2})T(d{2}):(d{2}):(d{2}(?:.d*)?)(?:([+-])(d{2}):(d{2}))?Z?$/ .exec(value); if (a) { var utcMilliseconds = Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6]); return new Date(utcMilliseconds); } } return value; }
The rather hairy regular expression on the third line matches dates in the ISO format, extracting each of the fields. If the regular expression finds a match, it extracts each of the date fields, which are then used by the Date class's UTC method to create a new date.
Note that the entire regular expression—everything between the/characters—should be on one line with no whitespace. It's a little long for this page, however!
For more information on how Json.NET handles dates and times, see the documentation and example at http://www.newtonsoft.com/json/help/html/SerializeDateFormatHandling.htm.
Like Json.NET, gson provides a way to specify the destination class to which you're deserializing a JSON object.
You'll need to include the gson JAR file in your application, just as you would for any other external API.
You use the same method as you use for type-unsafe JSON parsing using gson using fromJson, except you pass the class object to gson as the second argument, like this:
// Assuming we have a class Record that looks like this: /* class Record { private String call; private float lat; private float lng; // public API would access these fields } */ Gson gson = new com.google.gson.Gson(); String json = "{ "call": "kf6gpe-9", "lat": 21.9749, "lng": 159.3686 }"; Record result = gson.fromJson(json, Record.class);
The fromGson method always takes a Java class. In the example in this recipe, we convert directly to a plain old Java object that our application can use without needing to use the dereferencing and type conversion interface of JsonElement that gson provides.
The gson library can also deal with nested types and arrays as well. You can also hide fields from being serialized or deserialized by declaring them transient, which makes sense because transient fields aren't serialized.
The documentation for gson and its support for deserializing instances of classes is at https://sites.google.com/site/gson/gson-user-guide#TOC-Object-Examples.
Using TypeScript with Visual Studio is easy; it's just part of the installation of Visual Studio for any version after Visual Studio 2013 Update 2. Getting the TypeScript compiler for Node.js is almost as easy—it's an npm install away.
On a command line with npm in your path, run the following command:
npm install –g typescript
The npm option –g tells npm to install the TypeScript compiler globally, so it's available to every Node.js application you write. Once you run it, npm downloads and installs the TypeScript compiler binary for your platform.
Once you run this command to install the compiler, you'll have the TypeScript compiler tsc available on the command line. Compiling a file with tsc is as easy as writing the source code and saving in a file that ends in .ts extension, and running tsc on it. For example, given the following TypeScript saved in the file hello.ts:
function greeter(person: string) { return "Hello, " + person; } var user: string = "Ray"; console.log(greeter(user));
Running tschello.ts at the command line creates the following JavaScript:
function greeter(person) { return "Hello, " + person; } var user = "Ray"; console.log(greeter(user));
Try it!
As we'll see in the next section, the function declaration for greeter contains a single TypeScript annotation; it declares the argument person to be string. Add the following line to the bottom of hello.ts:
console.log(greeter(2));
Now, run the tschello.ts command again; you'll get an error like this one:
C:UsersrarischpDocumentsnode.jstypescripthello.ts(8,13): error TS2082:
Supplied parameters do not match any signature of call target: Could not apply type 'string' to argument 1 which is of type 'number'. C:UsersrarischpDocumentsnode.jstypescripthello.ts(8,13): error TS2087:
Could not select overload for 'call' expression.
This error indicates that I'm attempting to call greeter with a value of the wrong type, passing a number where greeter expects a string. In the next recipe, we'll look at the kinds of type annotations TypeScript supports for simple types.
The TypeScript home page, with tutorials and reference documentation, is at http://www.typescriptlang.org/.
Type annotations with TypeScript are simple decorators appended to the variable or function after a colon. There's support for the same primitive types as in JavaScript, and to declare interfaces and classes, which we will discuss next.
Here's a simple example of some variable declarations and two function declarations:
function greeter(person: string): string { return "Hello, " + person; } function circumference(radius: number) : number { var pi: number = 3.141592654; return 2 * pi * radius; } var user: string = "Ray"; console.log(greeter(user)); console.log("You need " + circumference(2) + " meters of fence for your dog.");
This example shows how to annotate functions and variables.
Variables—either standalone or as arguments to a function—are decorated using a colon and then the type. For example, the first function, greeter, takes a single argument, person, which must be a string. The second function, circumference, takes a radius, which must be a number, and declares a single variable in its scope, pi, which must be a number and has the value 3.141592654.
You declare functions in the normal way as in JavaScript, and then add the type annotation after the function name, again using a colon and the type. So, greeter returns a string, and circumference returns a number.
TypeScript defines the following fundamental type decorators, which map to their underlying JavaScript types:
var list:string[] = [ "one", "two", "three"];
enumColor { Red = 1, Green, Blue }; var c : Color = Color.Blue;
For a list of the TypeScript types, see the TypeScript handbook at http://www.typescriptlang.org/Handbook.
An interface defines how something behaves, without defining the implementation. In TypeScript, an interface names a complex type by describing the fields it has. This is known as structural subtyping.
Declaring an interface is a little like declaring a structure or class; you define the fields in the interface, each with its own type, like this:
interface Record { call: string; lat: number; lng: number; } Function printLocation(r: Record) { console.log(r.call + ': ' + r.lat + ', ' + r.lng); } var myObj = {call: 'kf6gpe-7', lat: 21.9749, lng: 159.3686}; printLocation(myObj);
The interface keyword in TypeScript defines an interface; as I already noted, an interface consists of the fields it declares with their types. In this listing, I defined a plain JavaScript object, myObj and then called the function printLocation, that I previously defined, which takes a Record. When calling printLocation with myObj, the TypeScript compiler checks the fields and types each field and only permits a call to printLocation if the object matches the interface.
Beware! TypeScript can only provide compile-type checking. What do you think the following code does?
interface Record { call: string; lat: number; lng: number; } Function printLocation(r: Record) { console.log(r.call + ': ' + r.lat + ', ' + r.lng); } var myObj = {call: 'kf6gpe-7', lat: 21.9749, lng: 159.3686}; printLocation(myObj); var json = '{"call":"kf6gpe-7","lat":21.9749}'; var myOtherObj = JSON.parse(json); printLocation(myOtherObj);
First, this compiles with tsc just fine. When you run it with node, you'll see the following:
kf6gpe-7: 21.9749, 159.3686 kf6gpe-7: 21.9749, undefined
What happened? The TypeScript compiler does not add run-time type checking to your code, so you can't impose an interface on a run-time created object that's not a literal. In this example, because the lng field is missing from the JSON, the function can't print it, and prints the value undefined instead.
This doesn't mean that you shouldn't use TypeScript with JSON, however. Type annotations serve a purpose for all readers of the code, be they compilers or people. You can use type annotations to indicate your intent as a developer, and readers of the code can better understand the design and limitation of the code you write.
For more information about interfaces, see the TypeScript documentation at http://www.typescriptlang.org/Handbook#interfaces.
Interfaces let you specify behavior without specifying implementation; classes let you encapsulate implementation details behind an interface. TypeScript classes can encapsulate fields or methods, just as classes in other languages.
Here's an example of our Record structure, this time as a class with an interface:
class RecordInterface { call: string; lat: number; lng: number; constructor(c: string, la: number, lo: number) {} printLocation() {} } class Record implements RecordInterface { call: string; lat: number; lng: number; constructor(c: string, la: number, lo: number) { this.call = c; this.lat = la; this.lng = lo; } printLocation() { console.log(this.call + ': ' + this.lat + ', ' + this.lng); } } var myObj : Record = new Record('kf6gpe-7', 21.9749, 159.3686); myObj.printLocation();
The interface keyword, again, defines an interface just as the previous section shows. The class keyword, which you haven't seen before, implements a class; the optional implements keyword indicates that this class implements the interface RecordInterface.
Note that the class implementing the interface must have all of the same fields and methods that the interface prescribes; otherwise, it doesn't meet the requirements of the interface. As a result, our Record class includes fields for call, lat, and lng, with the same types as in the interface, as well as the methods constructor and printLocation.
The constructor method is a special method called when you create a new instance of the class using new. Note that with classes, unlike regular objects, the correct way to create them is by using a constructor, rather than just building them up as a collection of fields and values. We do that on the second to the last line of the listing, passing the constructor arguments as function arguments to the class constructor.
There's a lot more you can do with classes, including defining inheritance and creating public and private fields and methods. For more information about classes in TypeScript, see the documentation at http://www.typescriptlang.org/Handbook#classes.
This last recipe is more of a tip than a recipe; if you've got some JSON you developed using another programming language or by hand, you can easily create a TypeScript interface for objects to contain the JSON by using Timmy Kokke's json2ts website.
Simply go to http://json2ts.com and paste your JSON in the box that appears, and click on the generate TypeScript button. You'll be rewarded with a second text-box that appears and shows you the definition of the TypeScript interface, which you can save as its own file and include in your TypeScript applications.
The following figure shows a simple example:
You can save this typescript as its own file, a definition file, with the suffix .d.ts, and then include the module with your TypeScript using the import keyword, like this:
import module = require('module');
In this article we looked at how you can adapt the type-free nature of JSON with the type safety provided by languages such as C#, Java, and TypeScript to reduce programming errors in your application.
Further resources on this subject: