Skip to content

Commit

Permalink
feat: added encoding functionality (#13)
Browse files Browse the repository at this point in the history
* chore: add encoding functionality

* chore: removed unused variables

* refactor: updated decoder functions parameters

* docs: updated readme file
  • Loading branch information
gorhom authored Apr 11, 2020
1 parent 7301b8c commit c692810
Show file tree
Hide file tree
Showing 14 changed files with 530 additions and 41 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ npm install @gorhom/codable
## Usage

```ts
import { BaseCodable, types, decode } from '@gorhom/codable';
import { BaseCodable, types, decode, encode } from '@gorhom/codable';
import dayjs from 'dayjs';

class Post extends BaseCodable {
Expand Down Expand Up @@ -86,12 +86,16 @@ const jsonPayload = {
};

const user: User = decode(User, jsonPayload);

// now encode it back 🙈

const userJson = encode(user)
```

## TODO

- [x] Add [Swift Decodable](https://developer.apple.com/documentation/swift/decodable) functionality.
- [ ] Add [Swift Encodable](https://developer.apple.com/documentation/swift/encodable) functionality.
- [x] Add [Swift Encodable](https://developer.apple.com/documentation/swift/encodable) functionality.
- [ ] Write API docs.

## Built With
Expand Down
2 changes: 1 addition & 1 deletion src/codable.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ICodingPropertyType, IDictionary, IBaseCodable } from './internal';

// @ts-ignore
export abstract class BaseCodable implements IBaseCodable {
public static CodingProperties: IDictionary<ICodingPropertyType>;
constructor(payload: object) {}
public toJSON = () => '';
}
1 change: 1 addition & 0 deletions src/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export * from './models';
export * from './utils/types';
export * from './utils/typecheck';
export * from './utils/decoder';
export * from './utils/encoder';
export * from './utils/errors';
export * from './utils/isEmpty';
export * from './utils/get';
20 changes: 17 additions & 3 deletions src/models/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
IModel,
IType,
decodeCodable,
encode as encodeCodable,
errors,
} from '../internal';

Expand Down Expand Up @@ -33,21 +34,34 @@ export const array = (type: IType): IModel => {
value[0]
);
};

const decode = (key: string, value: any[]) => {
if (validate(key, value)) {
const { subtype } = type;
if (subtype !== undefined && isCodable(subtype)) {
return value.map(item => decodeCodable(subtype, item, false, key));
return value.map(item =>
decodeCodable({
type: subtype,
json: item,
isRoot: false,
key,
})
);
}
return value;
} else {
return undefined;
}
};

const encode = (key: string, value: any[]) => {
const { subtype } = type;
if (subtype !== undefined && isCodable(subtype)) {
return value.map(item => encodeCodable(item));
}
return value;
};
return {
validate,
decode,
encode,
};
};
3 changes: 2 additions & 1 deletion src/models/boolean.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ export const boolean = (type: IType): IModel => {
return true;
};
const decode = (key: string, value: any) => value;

const encode = (key: string, value: any) => value;
return {
validate,
decode,
encode,
};
};
4 changes: 2 additions & 2 deletions src/models/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@ export const date = (type: IType): IModel => {
}
return true;
};

const decode = (key: string, value: any) => {
try {
return type.parser!(value);
} catch (error) {
throw errors.failToParse(key, value, error.message || error);
}
};

const encode = (key: string, value: any) => value;
return {
validate,
decode,
encode,
};
};
3 changes: 2 additions & 1 deletion src/models/number.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ export const number = (type: IType): IModel => {
return true;
};
const decode = (key: string, value: any) => value;

const encode = (key: string, value: any) => value;
return {
validate,
decode,
encode,
};
};
10 changes: 7 additions & 3 deletions src/models/optional.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,20 @@ export const optional = (type: IType): IModel => {
value
);
};

const decode = (key: string, value: object) =>
value === undefined
? undefined
: validate(key, value)
? decodeValue(key, type.subtype!, value)
? decodeValue({
key,
type: type.subtype!,
value,
})
: undefined;

const encode = (key: string, value: any) => value;
return {
validate,
decode,
encode,
};
};
3 changes: 2 additions & 1 deletion src/models/string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ export const string = (type: IType): IModel => {
return true;
};
const decode = (key: string, value: any) => value;

const encode = (key: string, value: any) => `${value}`;
return {
validate,
decode,
encode,
};
};
73 changes: 48 additions & 25 deletions src/utils/decoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,34 @@ import { IModel, INewable, IBaseCodable, ICodable } from './types';
export const decode = <T extends BaseCodable>(
type: INewable<T>,
json: any
): T & IBaseCodable => decodeCodable(type, json, true);
): T & IBaseCodable =>
decodeCodable({
type,
json,
isRoot: true,
});

export const decodeCodable = <T extends BaseCodable>(
type: INewable<T>,
json: any,
isRoot: boolean,
key?: string
): T & IBaseCodable => {
export const decodeCodable = <T extends BaseCodable>({
type,
json,
isRoot,
key,
}: {
type: INewable<T>;
json: any;
isRoot: boolean;
key?: string;
}): T & IBaseCodable => {
if (isRoot === false && isEmpty(json) === true) {
throw errors.missingValue(key || '', typeof type);
}

let result = new type(json);

// @ts-ignore
if (json !== undefined && type.CodingProperties !== undefined) {
// @ts-ignore
Object.assign(result, decodePayload(json, type.CodingProperties));
}

// @ts-ignore
return result;
};

Expand All @@ -43,32 +51,47 @@ export const decodePayload = (
) => {
return Object.keys(codingProperties)
.map(key => ({
[key]: decodeProperty(key, codingProperties[key], payload),
[key]: decodeProperty({
key,
codingProperty: codingProperties[key],
payload,
}),
}))
.reduce((properties, property) => ({ ...properties, ...property }), {});
};

export const decodeProperty = (
key: string,
codingProperty: ICodingPropertyType,
payload?: object
) => {
export const decodeProperty = ({
key,
codingProperty,
payload,
}: {
key: string;
codingProperty: ICodingPropertyType;
payload?: object;
}) => {
const jsonKey = get(codingProperty, 'key', key);
const value = get(payload, jsonKey, undefined);
const type: IType | ICodable = get(codingProperty, 'type', codingProperty);

return decodeValue(jsonKey, type, value);
return decodeValue({ key: jsonKey, type, value });
};

export const decodeValue = (
key: string,
type: IType | ICodable,
value?: object
) => {
export const decodeValue = ({
key,
type,
value,
}: {
key: string;
type: IType | ICodable;
value?: object;
}) => {
if (isCodable(type)) {
return decodeCodable(type, value, false, key);
return decodeCodable({
type,
json: value,
isRoot: false,
key,
});
}

const model: IModel = models[type.name](type);
return model.validate(key, value) ? model.decode(key, value) : undefined;
};
83 changes: 83 additions & 0 deletions src/utils/encoder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import {
models,
IType,
ICodingPropertyType,
isCodable,
BaseCodable,
get,
errors,
} from '../internal';
import { IModel, ICodable } from './types';

export const encode = <T extends BaseCodable>(codable: T): any => {
if (!(codable instanceof BaseCodable)) {
throw errors.invalidCodable();
}

// @ts-ignore
if (!codable.__proto__.constructor.CodingProperties) {
throw errors.missingCodingProperties();
}

// @ts-ignore
const codingProperties = codable.__proto__.constructor.CodingProperties;

// @ts-ignore
return Object.keys(codingProperties)
.map(key =>
// @ts-ignore
encodeProperty({
key,
codingProperty: codingProperties[key],
codable,
})
)
.reduce((result, item) => {
// @ts-ignore
result[item.key] = item.value;
return result;
}, {});
};

const encodeProperty = ({
key,
codingProperty,
codable,
}: {
key: string;
codingProperty: ICodingPropertyType;
codable: BaseCodable;
}) => {
const jsonKey = get(codingProperty, 'key', key);
const type: IType | ICodable = get(codingProperty, 'type', codingProperty);

return encodeValue({
key: jsonKey,
type,
// @ts-ignore
value: codable[key],
});
};

const encodeValue = ({
key,
type,
value,
}: {
key: string;
type: IType | ICodable;
value?: object;
}) => {
if (isCodable(type)) {
return {
key,
// @ts-ignore
value: encode(value),
};
}
const model: IModel = models[type.name](type);
return {
key,
value: model.encode(key, value),
};
};
2 changes: 2 additions & 0 deletions src/utils/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ export const errors = {
`Missing date parser. key: '${key}', type: ${type}`,
failToParse: (key: string, value: string, errorMessage: string) =>
`Fail to parse date with custom parser. key: '${key}', value: '${value}', parser error: '${errorMessage}'`,
invalidCodable: () => `Invalid codable type.`,
missingCodingProperties: () => `Missing 'CodingProperties' static variable.`,
};
3 changes: 2 additions & 1 deletion src/utils/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface IType {
export interface IModel {
validate: (key: string, value: any) => boolean;
decode: (key: string, value: any) => any;
encode: (key: string, value: any) => any;
}

export type IModelDictionary = {
Expand All @@ -33,7 +34,7 @@ export interface IDictionary<T> {
}

export interface IBaseCodable {
toJSON: () => string;
CodingProperties: IDictionary<ICodingPropertyType>;
}

interface IBaseCodableStatic {
Expand Down
Loading

0 comments on commit c692810

Please sign in to comment.