Skip to content

Commit

Permalink
Fix deserialization from object
Browse files Browse the repository at this point in the history
  • Loading branch information
Neos3452 committed Dec 9, 2018
1 parent eb63e64 commit f0595f4
Show file tree
Hide file tree
Showing 9 changed files with 198 additions and 12 deletions.
22 changes: 18 additions & 4 deletions js/typedjson.js

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

2 changes: 1 addition & 1 deletion js/typedjson.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/typedjson.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/typedjson.min.js.map

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions js/typedjson/helpers.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export declare function isDirectlySerializableNativeType(type: Function): boolea
export declare function isTypeTypedArray(type: Function): boolean;
export declare function isPrimitiveValue(obj: any): boolean;
export declare function isObject(value: any): value is Object;
export declare function parseToJSObject(json: any, expectedType: Function): Object;
/**
* Determines if 'A' is a sub-type of 'B' (or if 'A' equals 'B').
* @param A The supposed derived type.
Expand Down
94 changes: 94 additions & 0 deletions spec/just-json.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { jsonArrayMember, jsonMember, jsonObject, TypedJSON } from "../js/typedjson";

describe('json (without automatic stringify)', function () {
describe('string', function () {
it('should deserialize', function () {
// stringified json version cuz ""
expect(TypedJSON.parse('"str"', String)).toEqual('str');
// already parsed
expect(TypedJSON.parse('str', String)).toEqual('str');

// because we detect naively
try {
expect(TypedJSON.parse('"sdfs"fdsf"', String)).toEqual(undefined);
fail();
} catch(e) {}
});

it('should serialize', function () {
expect(TypedJSON.toPlainJson('str', String)).toEqual('str');
});
});

describe('rest of primitives', function () {
it('should deserialize', function () {
expect(TypedJSON.parse(45834, Number)).toEqual(45834);
expect(TypedJSON.parse(true, Boolean)).toEqual(true);
expect(TypedJSON.parse(1543915254, Date)).toEqual(new Date(1543915254));
expect(TypedJSON.parse('1970-01-18T20:51:55.254Z', Date)).toEqual(new Date(1543915254));

const dataBuffer = Uint8Array.from([100, 117, 112, 97]) as any;
expect(TypedJSON.parse('畤慰', ArrayBuffer)).toEqual(dataBuffer);
expect(TypedJSON.parse('畤慰', DataView)).toEqual(dataBuffer);
expect(TypedJSON.parse([100, 117, 112, 97], Uint8Array)).toEqual(dataBuffer);
});

it('should serialize', function () {
expect(TypedJSON.toPlainJson(45834, Number)).toEqual(45834);
expect(TypedJSON.toPlainJson(true, Boolean)).toEqual(true);
const dateMs = new Date(1543915254);
expect(TypedJSON.toPlainJson(dateMs, Date)).toEqual(dateMs);
const dateStr = new Date('2018-12-04T09:20:54');
expect(TypedJSON.toPlainJson(dateStr, Date)).toEqual(dateStr);

const buffer = new ArrayBuffer(4);
const view = new DataView(buffer);
view.setInt8(0, 100);
view.setInt8(1, 117);
view.setInt8(2, 112);
view.setInt8(3, 97);
expect(TypedJSON.toPlainJson(buffer, ArrayBuffer)).toEqual('畤慰');
expect(TypedJSON.toPlainJson(view, DataView)).toEqual('畤慰');
expect(TypedJSON.toPlainJson(new Uint8Array(buffer), Uint8Array)).toEqual([100, 117, 112, 97]);
});
});

describe('object', function () {

@jsonObject
class SomeThing {
@jsonMember
propStr: String;
@jsonMember
propNum: number;
@jsonArrayMember(String)
propArr: String[];
}

const json = Object.freeze({
propStr: 'dsgs',
propNum: 653,
propArr: ['dslfks'],
});

it('should deserialize', function () {
expect(TypedJSON.parse(json, SomeThing)).toEqual(Object.assign(new SomeThing(), json));
expect(TypedJSON.parseAsArray([json], SomeThing)).toEqual([Object.assign(new SomeThing(), json)]);
});

it('should serialize', function () {
expect(TypedJSON.toPlainJson(Object.assign(new SomeThing(), json), SomeThing)).toEqual(json);
expect(TypedJSON.toPlainArray([Object.assign(new SomeThing(), json)], SomeThing)).toEqual([json]);
});
});

describe('array', function () {
it('should deserialize', function () {
expect(TypedJSON.parseAsArray(['alas', 'dfsd'], String)).toEqual(['alas', 'dfsd']);
});

it('should serialize', function () {
expect(TypedJSON.toPlainArray(['alas', 'dfsd'], String)).toEqual(['alas', 'dfsd']);
});
});
});
58 changes: 58 additions & 0 deletions spec/parse-to-object.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { parseToJSObject } from '../src/typedjson/helpers';

describe("parse To Object", function () {
it("should passthrough objects", function () {
const obj = {
a: 1,
b: 2,
};

const obj2 = parseToJSObject(obj, Object);
expect(obj2).toBe(obj);
});

it("should passthrough arrays", function () {
const arr = [{
a: 1,
b: 2,
}];

const arr2 = parseToJSObject(arr, Array);
expect(arr2).toBe(arr);
});

it("should parse object string", function () {
const arr = {
a: 1,
b: 2,
};

const arr2 = parseToJSObject(JSON.stringify(arr), Object);
expect(arr2).toEqual(arr);
});

it("should passthrough primitives", function () {
expect(parseToJSObject(1, Number)).toBe(1);
expect(parseToJSObject(false, Boolean)).toBe(false);
});

it("should parse strings with quotes, but passthrough other", function () {
// string is obvious
expect(parseToJSObject('"I am a string"', String)).toEqual('I am a string');
expect(parseToJSObject('just a string', String)).toBe('just a string');
// but also the types that are serialized to string
expect(parseToJSObject('"1970-01-18T20:51:55.254Z"', Date)).toEqual('1970-01-18T20:51:55.254Z');
expect(parseToJSObject('1970-01-18T20:51:55.254Z', Date)).toBe('1970-01-18T20:51:55.254Z');
expect(parseToJSObject('"畤慰"', ArrayBuffer)).toEqual('畤慰');
expect(parseToJSObject('畤慰', ArrayBuffer)).toBe('畤慰');
expect(parseToJSObject('"畤慰"', DataView)).toEqual('畤慰');
expect(parseToJSObject('畤慰', DataView)).toBe('畤慰');
});

it("should passthrough builtins", function () {
const date = new Date;
expect(parseToJSObject(date, Date)).toBe(date);
const buffer = new ArrayBuffer(3);
expect(parseToJSObject(buffer, ArrayBuffer)).toBe(buffer);
});
});
10 changes: 5 additions & 5 deletions src/typedjson.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { nameof, logError, logWarning } from './typedjson/helpers';
import { nameof, logError, logWarning, parseToJSObject } from './typedjson/helpers';
import { Constructor } from "./typedjson/types";
import { JsonObjectMetadata } from "./typedjson/metadata";
import { Deserializer } from "./typedjson/deserializer";
Expand Down Expand Up @@ -318,7 +318,7 @@ export class TypedJSON<T>
*/
public parse(object: any): T|undefined
{
const json = JSON.parse(object);
const json = parseToJSObject(object, this.rootConstructor);

let rootMetadata = JsonObjectMetadata.getFromConstructor(this.rootConstructor);
let result: T|undefined;
Expand Down Expand Up @@ -360,7 +360,7 @@ export class TypedJSON<T>
public parseAsArray(object: any, dimensions: number): any[];
public parseAsArray(object: any, dimensions: number = 1): any[]
{
const json = JSON.parse(object);
const json = parseToJSObject(object, Array);
if (json instanceof Array)
{
return this.deserializer.convertAsArray(json, {
Expand All @@ -382,7 +382,7 @@ export class TypedJSON<T>

public parseAsSet(object: any): Set<T>
{
const json = JSON.parse(object);
const json = parseToJSObject(object, Set);
// A Set<T> is serialized as T[].
if (json instanceof Array)
{
Expand All @@ -404,7 +404,7 @@ export class TypedJSON<T>

public parseAsMap<K>(object: any, keyConstructor: Constructor<K>): Map<K, T>
{
const json = JSON.parse(object);
const json = parseToJSObject(object, Map);
// A Set<T> is serialized as T[].
if (json instanceof Array)
{
Expand Down
19 changes: 19 additions & 0 deletions src/typedjson/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,25 @@ export function isObject(value: any): value is Object
return typeof value === "object";
}

function shouldOmitParseString(jsonStr: string, expectedType: Function): boolean {
const expectsTypesSerializedAsStrings = expectedType === String
|| expectedType === ArrayBuffer
|| expectedType === DataView;

const hasQuotes = jsonStr.length >= 2 && jsonStr[0] === '"' && jsonStr[jsonStr.length-1] === '"';
const isInteger = /^\d+$/.test(jsonStr.trim());

return (expectsTypesSerializedAsStrings && !hasQuotes) || ((!hasQuotes && !isInteger) && expectedType === Date);
}

export function parseToJSObject(json: any, expectedType: Function): Object {
if (typeof json !== 'string' || shouldOmitParseString(json, expectedType))
{
return json;
}
return JSON.parse(json);
}

/**
* Determines if 'A' is a sub-type of 'B' (or if 'A' equals 'B').
* @param A The supposed derived type.
Expand Down

0 comments on commit f0595f4

Please sign in to comment.