Skip to content

Commit

Permalink
feat: toIndexedKeysMessage(plainObject, schema)
Browse files Browse the repository at this point in the history
  • Loading branch information
kpietraszko committed Dec 29, 2024
1 parent 01bc198 commit a142b37
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 93 deletions.
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@
# schemind
Read and write to messages serialized as arrays (aka indexed keys) by defining a schema.

### What?
## What?
TODO

### Installation
## Installation

```shell
npm install schemind
```

### Usage
## Usage
TODO

### Related work
## FAQ
TODO

## Related work
* https://github.com/MessagePack-CSharp/MessagePack-CSharp#use-indexed-keys-instead-of-string-keys-contractless
* https://aarnott.github.io/Nerdbank.MessagePack/docs/customizing-serialization.html?q=indexed#serialize-objects-with-indexes-for-keys

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "schemind",
"version": "0.0.0",
"description": "Easily read and write to messages serialized as arrays (indexed keys) by defining a schema; \nenabling smaller message size when using protocols such as msgpack or JSON.",
"description": "Read and write to messages serialized as arrays (indexed keys) by defining a schema; \nenabling smaller message size when using protocols such as msgpack or JSON.",
"keywords": [ "serialization", "schema", "index", "indexed keys", "array", "msgpack", "pack", "packed" ],
"license": "MIT",
"author": "kpietraszko",
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { withIndex, get, set, toObject } from './indexedKeysSchema'
export { withIndex, get, set, toPlainObject, toIndexedKeysMessage } from './indexedKeysSchema'
30 changes: 28 additions & 2 deletions src/indexedKeysSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,22 +88,48 @@ type PlainObjectOfSchema<TSchema> = TSchema extends IndexedKeysMessageSchema<unk
}
: never;

export function toObject<TSchema extends IndexedKeysMessageSchema<TSchemaInner>, TSchemaInner>(
export function toPlainObject<TSchema extends IndexedKeysMessageSchema<TSchemaInner>, TSchemaInner>(
message: readonly unknown[],
schema: TSchema): PlainObjectOfSchema<TSchema> {

const object: Partial<PlainObjectOfSchema<TSchema>> = {};

for (const [fieldName, nestedSchemaNode] of Object.entries(schema)) {
const nestedNode = nestedSchemaNode as IndexedKeysMessageSchema<unknown> | SchemaLeaf<unknown>;
let valueToSet = undefined;
if (isSchemaLeaf(nestedNode)) {
valueToSet = get(message, nestedNode);
} else {
valueToSet = toObject(message, nestedNode);
valueToSet = toPlainObject(message, nestedNode);
}

object[fieldName as keyof PlainObjectOfSchema<TSchema>] = valueToSet as any;
}

return object as PlainObjectOfSchema<TSchema>;
}

export function toIndexedKeysMessage<TSchema extends IndexedKeysMessageSchema<TSchemaInner>, TSchemaInner>(
plainObject: PlainObjectOfSchema<TSchema>,
schema: TSchema): unknown[] {

const message: unknown[] = [];
populateIndexedKeysMessage(message, plainObject, schema);
return message;
}

function populateIndexedKeysMessage<TSchema extends IndexedKeysMessageSchema<TSchemaInner>, TSchemaInner>(
messageToPopulate: unknown[],
plainObject: PlainObjectOfSchema<TSchema>,
schema: TSchema) {

for (const [fieldName, nestedSchemaNode] of Object.entries(schema)) {
const nestedNode = nestedSchemaNode as IndexedKeysMessageSchema<unknown> | SchemaLeaf<unknown>;
const leafValueOrSubObject = plainObject[fieldName as keyof PlainObjectOfSchema<TSchema>];
if (isSchemaLeaf(nestedNode)) {
set(messageToPopulate, nestedNode, leafValueOrSubObject);
} else {
populateIndexedKeysMessage(messageToPopulate, leafValueOrSubObject as PlainObjectOfSchema<TSchema>, nestedNode)
}
}
}
181 changes: 96 additions & 85 deletions test/indexedKeysSchema.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,60 @@
import { describe, it, expect, expectTypeOf } from "vitest";
import { withIndex as i, get, set, toObject } from "../src/index";
import { withIndex as i, get, set, toPlainObject, toIndexedKeysMessage } from "../src/index";

const someDate = new Date();
const message = [
420,
69,
"nice",
true,
["quick", "brown", "fox"],
[
1234567891234567,
someDate,
[
2138,
false,
[2, 3, 5, 8]
]
]] as unknown[];

type ExpectedObjectType = {
someNumber: number,
anotherNumber: number,
someArray: string[],
nestedThing: {
someNestedDate: Date,
evenMoreNestedThing: {
moreNestedNumber: number,
moreNestedBool: boolean,
moreNestedArray: number[]
},
someNestedNumber: number
},
someString: string,
someBool: boolean
};

const messageAsPlainObject = {
someNumber: 420,
anotherNumber: 69,
someArray: ["quick", "brown", "fox"],
nestedThing: {
someNestedDate: someDate,
evenMoreNestedThing: {
moreNestedNumber: 2138,
moreNestedBool: false,
moreNestedArray: [2, 3, 5, 8]
},
someNestedNumber: 1234567891234567
},
someString: "nice",
someBool: true
} satisfies ExpectedObjectType;

describe("get", () => {
it("should return value from the index - and of type - specified by the schema", () => {
const schema = createTestSchema();
const someDate = new Date();
const message = [420, 69, "nice", true, [
1234567891234567,
someDate,
[
2138,
false,
[2, 3, 5, 8]
]
]] as unknown[];

const r1 = get(message, schema.anotherNumber);
expectTypeOf(r1).toBeNumber();
Expand All @@ -30,118 +71,87 @@ describe("get", () => {
const r4 = get(message, schema.someString);
expectTypeOf(r4).toBeString();
expect(r4).to.equal("nice");

const r5 = get(message, schema.someArray);
expectTypeOf(r5).toEqualTypeOf<string[]>();
expect(r5).to.deep.equal(["quick", "brown", "fox"]);

const r5 = get(message, schema.nestedThing.someNestedNumber);
expectTypeOf(r5).toBeNumber();
expect(r5).to.equal(1234567891234567);
const r6 = get(message, schema.nestedThing.someNestedNumber);
expectTypeOf(r6).toBeNumber();
expect(r6).to.equal(1234567891234567);

const r6 = get(message, schema.nestedThing.someNestedDate);
expectTypeOf(r6).toEqualTypeOf<Date>();
expect(r6).to.equal(someDate);
const r7 = get(message, schema.nestedThing.someNestedDate);
expectTypeOf(r7).toEqualTypeOf<Date>();
expect(r7).to.equal(someDate);

const r7 = get(message, schema.nestedThing.evenMoreNestedThing.moreNestedNumber);
expectTypeOf(r7).toBeNumber();
expect(r7).to.equal(2138);
const r8 = get(message, schema.nestedThing.evenMoreNestedThing.moreNestedNumber);
expectTypeOf(r8).toBeNumber();
expect(r8).to.equal(2138);

const r8 = get(message, schema.nestedThing.evenMoreNestedThing.moreNestedBool);
expectTypeOf(r8).toBeBoolean();
expect(r8).to.equal(false);
const r9 = get(message, schema.nestedThing.evenMoreNestedThing.moreNestedBool);
expectTypeOf(r9).toBeBoolean();
expect(r9).to.equal(false);

const r9 = get(message, schema.nestedThing.evenMoreNestedThing.moreNestedArray);
expectTypeOf(r9).toEqualTypeOf<number[]>();
expect(r9).to.deep.equal([2, 3, 5, 8]);
const r10 = get(message, schema.nestedThing.evenMoreNestedThing.moreNestedArray);
expectTypeOf(r10).toEqualTypeOf<number[]>();
expect(r10).to.deep.equal([2, 3, 5, 8]);
});
});

describe("set", () => {
it("should place values at indexes specified by the schema", () => {
const schema = createTestSchema();
const someDate = new Date();

const newMessage = [] as unknown[];

set(newMessage, schema.someNumber, 420);
set(newMessage, schema.someString, "nice");
set(newMessage, schema.anotherNumber, 69);
set(newMessage, schema.someBool, true);
set(newMessage, schema.someArray, ["quick", "brown", "fox"]);
set(newMessage, schema.nestedThing.someNestedDate, someDate);
set(newMessage, schema.nestedThing.someNestedNumber, 1234567891234567);
set(newMessage, schema.nestedThing.evenMoreNestedThing.moreNestedBool, false);
set(newMessage, schema.nestedThing.evenMoreNestedThing.moreNestedNumber, 2138);
set(newMessage, schema.nestedThing.evenMoreNestedThing.moreNestedArray, [2, 3, 5, 8]);

const expectedMessage = [420, 69, "nice", true, [
1234567891234567,
someDate,
[
2138,
false,
[2, 3, 5, 8]
]
]] as unknown[];

const expectedMessage = message;
expect(newMessage).to.deep.equal(expectedMessage);
});
});

describe("toObject", () => {
it("should convert message to properly typed plain object", () => {
describe("toPlainObject", () => {
it("should convert a message to properly typed plain object", () => {
const schema = createTestSchema();
const someDate = new Date();
const message = [420, 69, "nice", true, [
1234567891234567,
someDate,
[
2138,
false,
[2, 3, 5, 8]
]
]] as unknown[];

type ExpectedObjectType = {
someNumber: number,
anotherNumber: number,
nestedThing: {
someNestedDate: Date,
evenMoreNestedThing: {
moreNestedNumber: number,
moreNestedBool: boolean,
moreNestedArray: number[]
},
someNestedNumber: number
},
someString: string,
someBool: boolean
};

const expectedObject = {
someNumber: 420,
anotherNumber: 69,
nestedThing: {
someNestedDate: someDate,
evenMoreNestedThing: {
moreNestedNumber: 2138,
moreNestedBool: false,
moreNestedArray: [2, 3, 5, 8]
},
someNestedNumber: 1234567891234567
},
someString: "nice",
someBool: true
}

const result = toObject(message, schema);


const result = toPlainObject(message, schema);

const expectedObject = messageAsPlainObject;

expectTypeOf(result).toEqualTypeOf<ExpectedObjectType>();
expect(result).to.deep.equal(expectedObject);
});
});

describe("toIndexedKeysMessage", () => {
it("should convert plain object to indexed-keys message", () => {
const schema = createTestSchema();

const result = toIndexedKeysMessage(messageAsPlainObject, schema);

const expectedMessage = message;

expectTypeOf(result).toEqualTypeOf<unknown[]>();
expect(result).to.deep.equal(expectedMessage);
});
});

export function createTestSchema() {
// the order of schema properties is intentionally shuffled here, to test that it doesn't matter
return {
someNumber: i(0)<number>(),
anotherNumber: i(1)<number>(),
nestedThing: i(4)({
nestedThing: i(5)({
someNestedDate: i(1)<Date>(),
evenMoreNestedThing: i(2)({
moreNestedNumber: i(0)<number>(),
Expand All @@ -151,6 +161,7 @@ export function createTestSchema() {
someNestedNumber: i(0)<number>(),
}),
someString: i(2)<string>(),
someArray: i(4)<string[]>(),
someBool: i(3)<boolean>()
};
}

0 comments on commit a142b37

Please sign in to comment.