Skip to content

Commit

Permalink
Split the src files, added custom converters and adjusted tests
Browse files Browse the repository at this point in the history
  • Loading branch information
andreas-aeschlimann committed Jul 19, 2017
1 parent 96ba86d commit 0527c25
Show file tree
Hide file tree
Showing 28 changed files with 1,805 additions and 810 deletions.
9 changes: 6 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# v0.9.7 (2017-07-15)
# v1.0.0 (2017-07-18)

## Bug Fixes

Expand All @@ -8,14 +8,17 @@
## Features

* Allow serialization the same way as deserialization, closes [#4](https://github.com/dhlab-basel/json2typescript/issues/4)
* Added smart methods `serialize()` and `deserialize()` for simplified usage
* Added custom converters, closes [#6](https://github.com/dhlab-basel/json2typescript/issues/6)
* Use class methods instead of static methods, closes [#14](https://github.com/dhlab-basel/json2typescript/issues/14)

## Breaking Changes

* Use an instance of `JsonConvert` its class methods instead of the static methods.
* Use an instance of `JsonConvert` its class methods instead of the static methods

* The property `valueCheckingMode` of the `JsonConvert` class needs to be set using the exported enum `ValueCheckingMode` instead of the inner class `JsonConvert.ValueCheckingMode`. If you would like to set the `valueCheckingMode`, please make the additional import of `ValueCheckingMode` and use this.
* The static class properties `valueCheckingMode` and `debugMode` are not static anymore. `debugMode` has been renamed to `operationMode`. Their values should be assigned through the given enums with the same name

* Removed the string method `deserializeString()` due to the fact that it is the same as `jsonConvert.deserialize()` combined with `JSON.stringify()`

# 0.9.6 (2017-01-18)
Fixed errors in ReadMe and small bug on JsonConvert.
Expand Down
81 changes: 62 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@ Type checking and object mapping is only possible in TypeScript, but not in the
As the API may change at any point, it is important for larger projects to verify the consumed data.

**json2typescript** is a small package containing a helper class that maps JSON objects to an instance of a TypeScript class.
After compiling to JavaScript, the result will still be an instance of this class.
After compiling to JavaScript, the result will still be an instance of this class. One big advantage of this approach is, that you can also use methods of this class.

With **json2typescript**, only a simple function call is necessary, as demonstrated in this TypeScript snippet:

```typescript
// Assume that you have a class named User defined at some point
// Assume that you get a JSON string from a webservice
let str: string = ...;
let jsonStr: string = ...;
let jsonObj: object = JSON.parse(jsonStr);

// Now you can map the string to the object automatically
let user: User = JsonConvert.deserializeString(str, User);
let user: User = JsonConvert.deserialize(jsonObj, User);
console.log(user); // prints User{ ... } in JavaScript runtime, not Object{ ... }
```

Expand Down Expand Up @@ -149,7 +150,7 @@ Then navigate to the file **app.component.ts** and add the following code:

```typescript
import {Component, OnInit} from '@angular/core';
import {JsonConvert} from "json2typescript"
import {JsonConvert, OperationMode, ValueCheckingMode} from "json2typescript"
import {Country} from "./country";

@Component({
Expand All @@ -159,9 +160,8 @@ import {Country} from "./country";
})
export class AppComponent implements OnInit {
ngOnInit() {
// Define a JSON string (could come from a HTTP service)
let jsonString = `
{
// Define a JSON object (could come from a HTTP service, parsed with JSON.parse() if necessary)
const jsonObject: object = {
"countryName": "Switzerland",
"cities": [
{
Expand All @@ -181,25 +181,26 @@ export class AppComponent implements OnInit {
"keywords": ["Limmat", "Lake"]
}
]
}`;
};

// Choose your settings
// Check the detailed reference in the chapter "JsonConvert class properties and methods"
JsonConvert.debugMode = true; // print some debug data
JsonConvert.ignorePrimitiveChecks = false; // don't allow assigning number to string etc.
JsonConvert.valueCheckingMode = JsonConvert.ValueCheckingMode.DISALLOW_NULL; // never allow null
let jsonConvert: JsonConvert = new JsonConvert();
jsonConvert.operationMode = OperationMode.LOGGING; // print some debug data
jsonConvert.ignorePrimitiveChecks = false; // don't allow assigning number to string etc.
jsonConvert.valueCheckingMode = ValueCheckingMode.DISALLOW_NULL; // never allow null

// Map to the country class
let country: Country;
try {
country = JsonConvert.deserializeString(jsonString, Country);
country = JsonConvert.deserialize(jsonObject, Country);
country.cities[0].printInfo(); // prints: Basel was founded in -200 and is really beautiful!
} catch (e) {
console.log((<Error>e));
}
}
```
Play around with the JSON to provocate exceptions when deserializing the string.
Play around with the JSON to provocate exceptions when deserializing the object.
---
Expand Down Expand Up @@ -233,17 +234,24 @@ export class User {
Note: You must assign any value or `undefined` to your property at initialization, otherwise our mapper does **not** work.
#### First parameter: jsonKey
#### First parameter: jsonProperty
The first parameter of `@JsonProperty` is the JSON object key.
The first parameter of `@JsonProperty` is the JSON object property.
It happens that the keys given by the server are very ugly.
Here you can map any key to the `User` property `name`.
In our case, `json[jsonKeyOfName]` gets mapped to `user[name]`.
In our case, `json["jsonKeyOfName"]` gets mapped to `user["name"]`.
#### Second parameter (optional): expectedType
#### Second parameter (optional): conversionOption
The second parameter of `@JsonProperty` describes what happens when doing the mapping between JSON and TypeScript objects.
This parameter is optional; the default value is undefined (which means no type check is done when the mapping happens).
##### Use of expected type
If you would like that `json2typescript` performs an automatic type
check according to given TypeScript types, you can pass a type you
expect.
The second parameter of `@JsonProperty` is the expected type.
This parameter is optional; the default value is undefined (which allows any type).
Make sure you pass the class name and not an instance of the class.
In case of primitive types, you have to use the upper case names.
See the following cheat sheet for reference:
Expand All @@ -266,6 +274,10 @@ At first, our array notation on the left looks odd.
But this notation allows you to define even nested arrays.
See the examples at the end of this document for more info about nesting arrays.
##### Adding a custom converter
More advanced users may need to use custom converters.
#### Third parameter (optional): isOptional
The third parameter of `@JsonProperty` determines whether the `jsonKey` has to be present in the json.
Expand Down Expand Up @@ -295,6 +307,37 @@ The type is still checked as soon the property is present again.
> Tip: See the examples at the end of this document for advanced examples for nesting arrays.
### Custom converter decorators
In some cases, you may need to make custom conversion between JSON objects and TypeScript objects. You can define custom converters like this:
```typescript
@JsonConverter
class DateConverter implements JsonCustomConvert<Date> {
serialize(date: Date): any {
return date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
}
deserialize(date: any): Date {
return new Date(date);
}
}
```
Assume that in your JSON you have a date in a standardized format, such as `2017-07-19 10:00:00`. You could use the custom converter class above to make sure it is stored as a real TypeScript date in your class. For your property, you simply have use the `@JsonProperty` decorator as follows:
```typescript
@JsonObject
export class User {
@JsonProperty("date", DateConverter)
public date: Date = undefined;
}
```
With this approach, you will achieve that your property `date` is going to be a real instance of `Date`.
## JsonConvert class properties and methods
### Public properties
Expand Down
7 changes: 0 additions & 7 deletions index.js

This file was deleted.

2 changes: 1 addition & 1 deletion index.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
export * from "./src/json2typescript/json-convert";
export { JsonConvert } from "./src/json2typescript/json-convert";
export { JsonCustomConvert } from "./src/json2typescript/json-custom-convert";
export { ValueCheckingMode, OperationMode } from "./src/json2typescript/json-convert-enums";
export { JsonObject, JsonProperty, JsonConverter } from "./src/json2typescript/json-convert-decorators";
4 changes: 4 additions & 0 deletions karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ module.exports = function (config) {
'text': '',
'html': 'coverage'
}
},
browserConsoleLogOptions: {
terminal: true,
level: ""
}
})
};
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "json2typescript",
"version": "0.9.7",
"description": "Provides TypeScript methods to map a JSON string to a JavaScript object on runtime",
"version": "1.0.0",
"description": "Provides TypeScript methods to map a JSON object to a JavaScript object on runtime",
"keywords": [
"convert",
"json",
Expand Down
63 changes: 63 additions & 0 deletions src/json2typescript/json-convert-decorators.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"use strict";
exports.__esModule = true;
var json_convert_options_1 = require("./json-convert-options");
/**
* Decorator of a class that is a custom converter.
*
* @param target the class
*/
function JsonConverter(target) {
target[json_convert_options_1.Settings.MAPPER_PROPERTY] = "";
}
exports.JsonConverter = JsonConverter;
/**
* Decorator of a class that comes from a JSON object.
*
* @param target the class
*/
function JsonObject(target) {
target[json_convert_options_1.Settings.MAPPING_PROPERTY] = [];
}
exports.JsonObject = JsonObject;
/**
* Decorator of a class property that comes from a JSON object.
*
* The second param can be either a type or a class of a custom converter.
*
* Use the following notation for the type:
* - Primitive type: String|Number|Boolean
* - Custom type: YourClassName
* - Array type: [String|Number|Boolean|YourClassName]
*
* If you decide to use a custom converter, make sure this class implements the interface JsonCustomConvert from this package.
*
* @param jsonProperty the key in the expected JSON object
* @param conversionOption optional param (default: undefined), should be either the expected type (String|Boolean|Number|etc) or a custom converter class implementing JsonCustomConvert
* @param isOptional optional param (default: false), if true, the json property does not have to be present in the object
*
* @returns {(target:any, key:string)=>void}
*/
function JsonProperty(jsonProperty, conversionOption, isOptional) {
return function (target, classProperty) {
if (typeof (target[json_convert_options_1.Settings.MAPPING_PROPERTY]) === "undefined") {
target[json_convert_options_1.Settings.MAPPING_PROPERTY] = [];
}
if (typeof (isOptional) === "undefined") {
isOptional = false;
}
var jsonPropertyMappingOptions = new json_convert_options_1.MappingOptions();
jsonPropertyMappingOptions.classProperty = classProperty;
jsonPropertyMappingOptions.jsonProperty = jsonProperty;
jsonPropertyMappingOptions.isOptional = isOptional ? isOptional : false;
// Check if conversionOption is a type or a custom converter.
if (typeof (conversionOption) !== "undefined" && typeof (conversionOption[json_convert_options_1.Settings.MAPPER_PROPERTY]) !== "undefined") {
jsonPropertyMappingOptions.customConverter = new conversionOption();
}
else {
jsonPropertyMappingOptions.expectedType = conversionOption;
}
// Save the mapping info
target[json_convert_options_1.Settings.MAPPING_PROPERTY][classProperty] = jsonPropertyMappingOptions;
};
}
exports.JsonProperty = JsonProperty;
1 change: 1 addition & 0 deletions src/json2typescript/json-convert-decorators.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

68 changes: 68 additions & 0 deletions src/json2typescript/json-convert-decorators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { MappingOptions, Settings } from "./json-convert-options";

/**
* Decorator of a class that is a custom converter.
*
* @param target the class
*/
export function JsonConverter(target: any) {
target[Settings.MAPPER_PROPERTY] = "";
}

/**
* Decorator of a class that comes from a JSON object.
*
* @param target the class
*/
export function JsonObject(target: any) {
target[Settings.MAPPING_PROPERTY] = [];
}

/**
* Decorator of a class property that comes from a JSON object.
*
* The second param can be either a type or a class of a custom converter.
*
* Use the following notation for the type:
* - Primitive type: String|Number|Boolean
* - Custom type: YourClassName
* - Array type: [String|Number|Boolean|YourClassName]
*
* If you decide to use a custom converter, make sure this class implements the interface JsonCustomConvert from this package.
*
* @param jsonProperty the key in the expected JSON object
* @param conversionOption optional param (default: undefined), should be either the expected type (String|Boolean|Number|etc) or a custom converter class implementing JsonCustomConvert
* @param isOptional optional param (default: false), if true, the json property does not have to be present in the object
*
* @returns {(target:any, key:string)=>void}
*/
export function JsonProperty(jsonProperty: string, conversionOption?: any, isOptional?: boolean): any {

return function (target: any, classProperty: string): void {

if (typeof(target[Settings.MAPPING_PROPERTY]) === "undefined") {
target[Settings.MAPPING_PROPERTY] = [];
}

if (typeof(isOptional) === "undefined") {
isOptional = false;
}

let jsonPropertyMappingOptions = new MappingOptions();
jsonPropertyMappingOptions.classProperty = classProperty;
jsonPropertyMappingOptions.jsonProperty = jsonProperty;
jsonPropertyMappingOptions.isOptional = isOptional ? isOptional : false;

// Check if conversionOption is a type or a custom converter.
if (typeof(conversionOption) !== "undefined" && typeof(conversionOption[Settings.MAPPER_PROPERTY]) !== "undefined") {
jsonPropertyMappingOptions.customConverter = new conversionOption();
} else {
jsonPropertyMappingOptions.expectedType = conversionOption;
}

// Save the mapping info
target[Settings.MAPPING_PROPERTY][classProperty] = jsonPropertyMappingOptions;

}

}
Loading

0 comments on commit 0527c25

Please sign in to comment.