diff --git a/README.md b/README.md index abbe341..0af37a7 100644 --- a/README.md +++ b/README.md @@ -7,4 +7,131 @@ jstruct ---- -TODO: Add here \ No newline at end of file +## Usage + +### Serialization + +Below we define a struct called `Person`: + +```d +struct Person +{ + public string firstname, lastname; + public int age; + public string[] list; + public JSONValue extraJSON; +} +``` + +Let's create a instance and set some values before we continue: + +```d +Person p1; +p1.firstname = "Tristan"; +p1.lastname = "Kildaire"; +p1.age = 23; +p1.list = ["1", "2", "3"]; +p1.extraJSON = parseJSON(`{"item":1, "items":[1,2,3]}`); +``` + +Now, we make a call to `serializeRecord` as follows: + +```d +JSONValue serialized = serializeRecord(p1); +``` + +This returns the following JSON: + +```json +{ + "age": 23, + "extraJSON": { + "item": 1, + "items": [ + 1, + 2, + 3 + ] + }, + "firstname": "Tristan", + "lastname": "Kildaire", + "list": "[\"1\", \"2\", \"3\"]" +} +``` + +### Deserialization + +Deserialization works by having your predefined struct type and then looking up those field names in the provided JSON. Therefore for this we will be using the following struct type: + +```d +struct Person +{ + public string firstname, lastname; + public int age; + public bool isMale; + public JSONValue obj; +} +``` + +Now, let's say we were given the following JSON: + +```json +{ + "firstname" : "Tristan", + "lastname": "Kildaire", + "age": 23, + "obj" : {"bruh":1}, + "isMale": true, +} +``` + +We can then deserialize the JSON to our type `Person`, with the `fromJSON` method: + +```d +// Define our JSON +JSONValue json = parseJSON(`{ +"firstname" : "Tristan", +"lastname": "Kildaire", +"age": 23, +"obj" : {"bruh":1}, +"isMale": true, +"list": [1,2,3] +} +`); + +// Deserialize +Person person = fromJSON!(Person)(json); +``` + +And we can confirm this with: + +```d +writeln(person): +``` + +Which will output: + +``` +Person("Tristan", "Kildaire", 23, true, {"bruh":1}) +``` + +## Installing + +In order to use jstruct in your project simply run: + +```bash +dub add jstruct +``` + +And then in your D program import as follows: + +```d +import jstruct; +``` + +## Help wanted + +There are some outstanding issues I want to be able to fix/have implemented, namely: + +- [ ] Support for array serialization/deserialization - see issue #1 +- [ ] Support for custom types serialization/deserialization (think `enums` for example) - see issue #2 \ No newline at end of file diff --git a/source/deserializer.d b/source/deserializer.d index 05299e2..cef2962 100644 --- a/source/deserializer.d +++ b/source/deserializer.d @@ -1,2 +1,205 @@ module jstruct.deserializer; +import std.json; +import jstruct.exceptions : SerializationError; + +import std.traits : FieldTypeTuple, FieldNameTuple; + +/** + * Deserializes the provided JSON into a struct of type RecordType + * + * Params: + * jsonIn = the JSON to deserialize + * Throws: + * RemoteFieldMissing = if the field names in the provided RecordType + * cannot be found within the prpvided JSONValue `jsonIn`. + * Returns: an instance of RecordType + */ +public RecordType fromJSON(RecordType)(JSONValue jsonIn) +{ + RecordType record; + + // Alias as to only expand later when used in compile-time + alias structTypes = FieldTypeTuple!(RecordType); + alias structNames = FieldNameTuple!(RecordType); + alias structValues = record.tupleof; + + static foreach(cnt; 0..structTypes.length) + { + debug(dbg) + { + pragma(msg, structTypes[cnt]); + pragma(msg, structNames[cnt]); + // pragma(msg, structValues[cnt]); + } + + debug(dbg) + { + pragma(msg, "Bruh type"); + pragma(msg, structTypes[cnt]); + // pragma(msg, __traits(identifier, mixin(structTypes[cnt]))); + } + + try + { + static if(__traits(isSame, mixin(structTypes[cnt]), byte)) + { + mixin("record."~structNames[cnt]) = cast(byte)jsonIn[structNames[cnt]].integer(); + } + else static if(__traits(isSame, mixin(structTypes[cnt]), ubyte)) + { + mixin("record."~structNames[cnt]) = cast(ubyte)jsonIn[structNames[cnt]].uinteger(); + } + else static if(__traits(isSame, mixin(structTypes[cnt]), short)) + { + mixin("record."~structNames[cnt]) = cast(short)jsonIn[structNames[cnt]].integer(); + } + else static if(__traits(isSame, mixin(structTypes[cnt]), ushort)) + { + mixin("record."~structNames[cnt]) = cast(ushort)jsonIn[structNames[cnt]].uinteger(); + } + else static if(__traits(isSame, mixin(structTypes[cnt]), int)) + { + mixin("record."~structNames[cnt]) = cast(int)jsonIn[structNames[cnt]].integer(); + } + else static if(__traits(isSame, mixin(structTypes[cnt]), uint)) + { + mixin("record."~structNames[cnt]) = cast(uint)jsonIn[structNames[cnt]].uinteger(); + } + else static if(__traits(isSame, mixin(structTypes[cnt]), ulong)) + { + mixin("record."~structNames[cnt]) = cast(ulong)jsonIn[structNames[cnt]].uinteger(); + } + else static if(__traits(isSame, mixin(structTypes[cnt]), long)) + { + mixin("record."~structNames[cnt]) = cast(long)jsonIn[structNames[cnt]].integer(); + } + else static if(__traits(isSame, mixin(structTypes[cnt]), string)) + { + mixin("record."~structNames[cnt]) = jsonIn[structNames[cnt]].str(); + + debug(dbg) + { + pragma(msg,"record."~structNames[cnt]); + } + } + else static if(__traits(isSame, mixin(structTypes[cnt]), JSONValue)) + { + mixin("record."~structNames[cnt]) = jsonIn[structNames[cnt]]; + + debug(dbg) + { + pragma(msg,"record."~structNames[cnt]); + } + } + else static if(__traits(isSame, mixin(structTypes[cnt]), bool)) + { + mixin("record."~structNames[cnt]) = jsonIn[structNames[cnt]].boolean(); + + debug(dbg) + { + pragma(msg,"record."~structNames[cnt]); + } + } + //FIXME: Not sure how to get array support going, very new to meta programming + else static if(__traits(isSame, mixin(structTypes[cnt]), mixin(structTypes[cnt])[])) + { + mixin("record."~structNames[cnt]) = jsonIn[structNames[cnt]].boolean(); + + debug(dbg) + { + pragma(msg,"record."~structNames[cnt]); + } + } + else + { + // throw new + //TODO: Throw error + debug(dbg) + { + pragma(msg, "Unknown type for de-serialization"); + } + } + } + catch(JSONException e) + { + throw new SerializationError(); + } + } + + return record; +} + +unittest +{ + import std.string : cmp; + import std.stdio : writeln; + + struct Person + { + public string firstname, lastname; + public int age; + public bool isMale; + public JSONValue obj; + public int[] list; + } + + JSONValue json = parseJSON(`{ +"firstname" : "Tristan", +"lastname": "Kildaire", +"age": 23, +"obj" : {"bruh":1}, +"isMale": true, +"list": [1,2,3] +} +`); + + Person person = fromJSON!(Person)(json); + + debug(dbg) + { + writeln(person); + } + + assert(cmp(person.firstname, "Tristan") == 0); + assert(cmp(person.lastname, "Kildaire") == 0); + assert(person.age == 23); + assert(person.isMale == true); + assert(person.obj["bruh"].integer() == 1); + //TODO: list test case +} + +unittest +{ + import std.string : cmp; + import std.stdio : writeln; + + struct Person + { + public string firstname, lastname; + public int age; + public bool isMale; + public JSONValue obj; + public int[] list; + } + + JSONValue json = parseJSON(`{ +"firstname" : "Tristan", +"lastname": "Kildaire", +"age": 23, +"obj" : {"bruh":1}, +"list": [1,2,3] +} +`); + + try + { + Person person = fromJSON!(Person)(json); + assert(false); + } + catch(SerializationError) + { + assert(true); + } + +} \ No newline at end of file diff --git a/source/exceptions.d b/source/exceptions.d new file mode 100644 index 0000000..5bb99e7 --- /dev/null +++ b/source/exceptions.d @@ -0,0 +1,17 @@ +module jstruct.exceptions; + +public abstract class JStructException : Exception +{ + this(string msg) + { + super("JStructException: "~msg); + } +} + +public final class SerializationError : JStructException +{ + this() + { + super("Errro serializing"); + } +} \ No newline at end of file diff --git a/source/serializer.d b/source/serializer.d index dc0ea49..15d7ca6 100644 --- a/source/serializer.d +++ b/source/serializer.d @@ -1,2 +1,109 @@ module jstruct.serializer; +import std.json; +import std.conv : to; +import std.traits : FieldTypeTuple, FieldNameTuple; + +public JSONValue serializeRecord(RecordType)(RecordType record) +{ + // Final JSON to submit + JSONValue builtJSON; + + // Alias as to only expand later when used in compile-time + alias structTypes = FieldTypeTuple!(RecordType); + alias structNames = FieldNameTuple!(RecordType); + alias structValues = record.tupleof; + + static foreach(cnt; 0..structTypes.length) + { + debug(dbg) + { + pragma(msg, structTypes[cnt]); + pragma(msg, structNames[cnt]); + // pragma(msg, structValues[cnt]); + } + + + static if(__traits(isSame, mixin(structTypes[cnt]), int)) + { + builtJSON[structNames[cnt]] = structValues[cnt]; + } + else static if(__traits(isSame, mixin(structTypes[cnt]), uint)) + { + builtJSON[structNames[cnt]] = structValues[cnt]; + } + else static if(__traits(isSame, mixin(structTypes[cnt]), ulong)) + { + builtJSON[structNames[cnt]] = structValues[cnt]; + } + else static if(__traits(isSame, mixin(structTypes[cnt]), long)) + { + builtJSON[structNames[cnt]] = structValues[cnt]; + } + else static if(__traits(isSame, mixin(structTypes[cnt]), string)) + { + builtJSON[structNames[cnt]] = structValues[cnt]; + } + else static if(__traits(isSame, mixin(structTypes[cnt]), JSONValue)) + { + builtJSON[structNames[cnt]] = structValues[cnt]; + } + else static if(__traits(isSame, mixin(structTypes[cnt]), bool)) + { + builtJSON[structNames[cnt]] = structValues[cnt]; + } + else + { + debug(dbg) + { + pragma(msg, "Yaa"); + } + builtJSON[structNames[cnt]] = to!(string)(structValues[cnt]); + } + } + + + return builtJSON; +} + +// Test serialization of a struct to JSON +private enum EnumType +{ + DOG, + CAT +} +unittest +{ + import std.algorithm.searching : canFind; + import std.string : cmp; + import std.stdio : writeln; + + struct Person + { + public string firstname, lastname; + public int age; + public string[] list; + public JSONValue extraJSON; + public EnumType eType; + } + + Person p1; + p1.firstname = "Tristan"; + p1.lastname = "Kildaire"; + p1.age = 23; + p1.list = ["1", "2", "3"]; + p1.extraJSON = parseJSON(`{"item":1, "items":[1,2,3]}`); + p1.eType = EnumType.CAT; + + JSONValue serialized = serializeRecord(p1); + + string[] keys = serialized.object().keys(); + assert(canFind(keys, "firstname") && cmp(serialized["firstname"].str(), "Tristan") == 0); + assert(canFind(keys, "lastname") && cmp(serialized["lastname"].str(), "Kildaire") == 0); + assert(canFind(keys, "age") && serialized["age"].integer() == 23); + + debug(dbg) + { + writeln(serialized.toPrettyString()); + } +}