diff --git a/README.md b/README.md index bb40ce6..dd5d37d 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/package.json b/package.json index ddd29ef..341dd97 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/index.ts b/src/index.ts index 292beef..2560b96 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1 @@ -export { withIndex, get, set, toObject } from './indexedKeysSchema' +export { withIndex, get, set, toPlainObject, toIndexedKeysMessage } from './indexedKeysSchema' diff --git a/src/indexedKeysSchema.ts b/src/indexedKeysSchema.ts index f3db1e2..fc5dadf 100644 --- a/src/indexedKeysSchema.ts +++ b/src/indexedKeysSchema.ts @@ -88,18 +88,19 @@ type PlainObjectOfSchema = TSchema extends IndexedKeysMessageSchema, TSchemaInner>( +export function toPlainObject, TSchemaInner>( message: readonly unknown[], schema: TSchema): PlainObjectOfSchema { const object: Partial> = {}; + for (const [fieldName, nestedSchemaNode] of Object.entries(schema)) { const nestedNode = nestedSchemaNode as IndexedKeysMessageSchema | SchemaLeaf; let valueToSet = undefined; if (isSchemaLeaf(nestedNode)) { valueToSet = get(message, nestedNode); } else { - valueToSet = toObject(message, nestedNode); + valueToSet = toPlainObject(message, nestedNode); } object[fieldName as keyof PlainObjectOfSchema] = valueToSet as any; @@ -107,3 +108,28 @@ export function toObject, return object as PlainObjectOfSchema; } + +export function toIndexedKeysMessage, TSchemaInner>( + plainObject: PlainObjectOfSchema, + schema: TSchema): unknown[] { + + const message: unknown[] = []; + populateIndexedKeysMessage(message, plainObject, schema); + return message; +} + +function populateIndexedKeysMessage, TSchemaInner>( + messageToPopulate: unknown[], + plainObject: PlainObjectOfSchema, + schema: TSchema) { + + for (const [fieldName, nestedSchemaNode] of Object.entries(schema)) { + const nestedNode = nestedSchemaNode as IndexedKeysMessageSchema | SchemaLeaf; + const leafValueOrSubObject = plainObject[fieldName as keyof PlainObjectOfSchema]; + if (isSchemaLeaf(nestedNode)) { + set(messageToPopulate, nestedNode, leafValueOrSubObject); + } else { + populateIndexedKeysMessage(messageToPopulate, leafValueOrSubObject as PlainObjectOfSchema, nestedNode) + } + } +} diff --git a/test/indexedKeysSchema.test.ts b/test/indexedKeysSchema.test.ts index 1cce11f..e4c2d9c 100644 --- a/test/indexedKeysSchema.test.ts +++ b/test/indexedKeysSchema.test.ts @@ -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(); @@ -30,33 +71,36 @@ 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(); + 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(); - expect(r6).to.equal(someDate); + const r7 = get(message, schema.nestedThing.someNestedDate); + expectTypeOf(r7).toEqualTypeOf(); + 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(); - expect(r9).to.deep.equal([2, 3, 5, 8]); + const r10 = get(message, schema.nestedThing.evenMoreNestedThing.moreNestedArray); + expectTypeOf(r10).toEqualTypeOf(); + 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[]; @@ -64,84 +108,50 @@ describe("set", () => { 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(); 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(); + 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)(), anotherNumber: i(1)(), - nestedThing: i(4)({ + nestedThing: i(5)({ someNestedDate: i(1)(), evenMoreNestedThing: i(2)({ moreNestedNumber: i(0)(), @@ -151,6 +161,7 @@ export function createTestSchema() { someNestedNumber: i(0)(), }), someString: i(2)(), + someArray: i(4)(), someBool: i(3)() }; }