Compare commits

...

34 Commits

Author SHA1 Message Date
Tristan B. Velloza Kildaire d99bfbaf35 Fix 2023-06-28 16:59:22 +02:00
Tristan B. Velloza Kildaire 2df7d833cf Make protected 2023-06-28 16:58:48 +02:00
Tristan B. Velloza Kildaire 1480a5afca - Updated example 2023-06-28 16:54:15 +02:00
Tristan B. Velloza Kildaire 73caebc501 Deserializer
- Ensure animal is right
2023-06-28 16:50:38 +02:00
Tristan B. Velloza Kildaire f7d9aee319 - Updated example in README 2023-06-28 16:50:30 +02:00
Tristan B. Velloza Kildaire c0bd2d624e Serializer
- Cleaned up

Deserializer

- Cleaned up
2023-06-28 16:44:00 +02:00
Tristan B. Velloza Kildaire b66c7741af Deserializer
- Cleaned up
- Removed now-completed comments
2023-06-28 16:21:30 +02:00
Tristan B. Velloza Kildaire ea079bbde4 Deserializer
- Added enum deserialization support
2023-06-28 12:25:39 +02:00
Tristan B. Velloza Kildaire 47b2a318da Package
- Documented public import

Exceptions

- Documented module and classes
2023-06-28 11:56:01 +02:00
Tristan B. Velloza Kildaire a15e0b5bcb Serializer
- Cleaned up
2023-06-28 11:55:09 +02:00
Tristan B. Velloza Kildaire dad7e04ccf Exceptions
- Added `DeserializationError`

Deserializer

- On error now throws `DeserializationError` exception
2023-06-28 11:51:57 +02:00
Tristan B. Velloza Kildaire 2f4570d0fa Deserializer
- Added `string[]` (`string`-array) deserialization support

Unittests

- Added string array example test
2023-06-24 21:40:38 +02:00
Tristan B. Velloza Kildaire 91cc5b5ce7 - Fixed docs 2023-06-24 14:50:51 +02:00
Tristan B. Velloza Kildaire 6bc3507dfe - Updated README example 2023-06-23 12:30:54 +02:00
Tristan B. Velloza Kildaire 16babe6ab0
Update README.md 2023-06-23 09:20:31 +02:00
Tristan B. Velloza Kildaire eb35dd4b13 Unit tests
- Document all
2023-06-23 09:17:29 +02:00
Tristan B. Velloza Kildaire 13fe2e1332 Deserialization
- Documented module

Serialization

- Documented module
2023-06-23 09:14:42 +02:00
Tristan B. Velloza Kildaire 9cb99935e0 Package
- Documented package module
2023-06-23 09:14:22 +02:00
Tristan B. Velloza Kildaire 7c4d16c14f Unit tests
- Added `float` and `double` unit test
2023-06-23 09:11:56 +02:00
Tristan B. Velloza Kildaire 2be4c3a84c Unit tests
- Added boolean unit test
2023-06-23 09:08:50 +02:00
Tristan B. Velloza Kildaire 0faf5a089b Deserializer
- Added `float` and `double` support
2023-06-23 09:07:23 +02:00
Tristan B. Velloza Kildaire d8b59673c3 Deserializer
- Added boolean support
2023-06-23 09:06:35 +02:00
Tristan B. Velloza Kildaire 498251d255 README
- Removed work-in-progress feature
2023-06-23 09:04:59 +02:00
Tristan B. Velloza Kildaire 6f64b2c19d Unit tests
- Added array deserialization test
2023-06-23 08:59:15 +02:00
Tristan B. Velloza Kildaire 72ba0cd7b5 Deserializer
- Added array deserialization support for integral types
2023-06-23 08:58:33 +02:00
Tristan B. Velloza Kildaire 838ecb2a08 Serializer
- Cleaned up
- Added documentation

Deserializer

- Fixed documentation
2023-06-23 08:38:15 +02:00
Tristan B. Velloza Kildaire 40118feffb Deserializer
- Removed unrequired mixins as these compiler tuples are not string-elemented but symbolic
- Disabled the broken array code
2023-06-23 08:35:21 +02:00
Tristan B. Velloza Kildaire b533e8ccee Merge branch 'master' of github.com:Hax-io/jstruct 2023-06-23 08:28:49 +02:00
Tristan B. Velloza Kildaire 023e7b7e8d Serializer
- There was no need to mix these in as they are symbols already, it would have made sense if they were strings
- Added support for array detection

Unit tests

- Added serialization check for the serialized array
2023-06-23 08:27:39 +02:00
Tristan B. Velloza Kildaire 879d9a4fa4
Update README.md 2023-03-25 22:46:33 +02:00
Tristan B. Velloza Kildaire 6d3cea3718
Create d.yml 2023-03-25 22:46:06 +02:00
Tristan B. Velloza Kildaire 169a0bb48f
Merge pull request #3 from ashcyber/patch-1
Update exceptions.d
2023-02-01 13:34:04 +02:00
Ash abf8f59cc7
Update exceptions.d
Correct spelling for Serialization error
2023-01-28 16:48:10 -05:00
Tristan B. Velloza Kildaire 940b449b7f - Added missing `exceptions` module include to package.d 2023-01-09 11:15:43 +02:00
6 changed files with 356 additions and 62 deletions

32
.github/workflows/d.yml vendored Normal file
View File

@ -0,0 +1,32 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
name: D
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dlang-community/setup-dlang@4c99aa991ce7d19dd3064de0a4f2f6b2f152e2d7
- name: 'Build & Test'
run: |
# Build the project, with its main file included, without unittests
dub build --compiler=$DC
# Build and run tests, as defined by `unittest` configuration
# In this mode, `mainSourceFile` is excluded and `version (unittest)` are included
# See https://dub.pm/package-format-json.html#configurations
dub test --compiler=$DC

View File

@ -7,6 +7,8 @@ jstruct
----
[![D](https://github.com/Hax-io/jstruct/actions/workflows/d.yml/badge.svg)](https://github.com/Hax-io/jstruct/actions/workflows/d.yml) ![DUB](https://img.shields.io/dub/v/jstruct?color=%23c10000ff%20&style=flat-square) ![DUB](https://img.shields.io/dub/dt/jstruct?style=flat-square) ![DUB](https://img.shields.io/dub/l/jstruct?style=flat-square)
## Usage
### Serialization
@ -20,6 +22,17 @@ struct Person
public int age;
public string[] list;
public JSONValue extraJSON;
public EnumType eType;
}
```
Our enum is defined as:
```d
enum EnumType
{
DOG,
CAT
}
```
@ -32,6 +45,7 @@ p1.lastname = "Kildaire";
p1.age = 23;
p1.list = ["1", "2", "3"];
p1.extraJSON = parseJSON(`{"item":1, "items":[1,2,3]}`);
p1.eType = EnumType.CAT;
```
Now, we make a call to `serializeRecord` as follows:
@ -45,6 +59,7 @@ This returns the following JSON:
```json
{
"age": 23,
"eType": "CAT",
"extraJSON": {
"item": 1,
"items": [
@ -55,7 +70,11 @@ This returns the following JSON:
},
"firstname": "Tristan",
"lastname": "Kildaire",
"list": "[\"1\", \"2\", \"3\"]"
"list": [
"1",
"2",
"3"
]
}
```
@ -70,6 +89,12 @@ struct Person
public int age;
public bool isMale;
public JSONValue obj;
public int[] list;
public bool[] list2;
public float[] list3;
public double[] list4;
public string[] list5;
public EnumType animal;
}
```
@ -82,6 +107,12 @@ Now, let's say we were given the following JSON:
"age": 23,
"obj" : {"bruh":1},
"isMale": true,
"list": [1,2,3],
"list2": [true, false],
"list3": [1.5, 1.4],
"list4": [1.5, 1.4],
"list5": ["baba", "booey"],
"animal": "CAT"
}
```
@ -95,7 +126,12 @@ JSONValue json = parseJSON(`{
"age": 23,
"obj" : {"bruh":1},
"isMale": true,
"list": [1,2,3]
"list": [1,2,3],
"list2": [true, false],
"list3": [1.5, 1.4],
"list4": [1.5, 1.4],
"list5": ["baba", "booey"],
"animal": "CAT"
}
`);
@ -112,7 +148,7 @@ writeln(person):
Which will output:
```
Person("Tristan", "Kildaire", 23, true, {"bruh":1})
Person("Tristan", "Kildaire", 23, true, {"bruh":1}, [1, 2, 3], [true, false], [1.5, 1.4], [1.5, 1.4], ["baba", "booey"], CAT)
```
## Installing
@ -128,10 +164,3 @@ 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

View File

@ -1,20 +1,23 @@
/**
* Deserialization utilities
*/
module jstruct.deserializer;
import std.json;
import jstruct.exceptions : SerializationError;
import std.traits : FieldTypeTuple, FieldNameTuple;
import jstruct.exceptions : DeserializationError;
import std.traits : FieldTypeTuple, FieldNameTuple, isArray, ForeachType, EnumMembers, fullyQualifiedName;;
import std.conv : to;
/**
* 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
*/
* 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;
@ -42,39 +45,39 @@ public RecordType fromJSON(RecordType)(JSONValue jsonIn)
try
{
static if(__traits(isSame, mixin(structTypes[cnt]), byte))
static if(__traits(isSame, structTypes[cnt], byte))
{
mixin("record."~structNames[cnt]) = cast(byte)jsonIn[structNames[cnt]].integer();
}
else static if(__traits(isSame, mixin(structTypes[cnt]), ubyte))
else static if(__traits(isSame, structTypes[cnt], ubyte))
{
mixin("record."~structNames[cnt]) = cast(ubyte)jsonIn[structNames[cnt]].uinteger();
}
else static if(__traits(isSame, mixin(structTypes[cnt]), short))
else static if(__traits(isSame, structTypes[cnt], short))
{
mixin("record."~structNames[cnt]) = cast(short)jsonIn[structNames[cnt]].integer();
}
else static if(__traits(isSame, mixin(structTypes[cnt]), ushort))
else static if(__traits(isSame, structTypes[cnt], ushort))
{
mixin("record."~structNames[cnt]) = cast(ushort)jsonIn[structNames[cnt]].uinteger();
}
else static if(__traits(isSame, mixin(structTypes[cnt]), int))
else static if(__traits(isSame, structTypes[cnt], int))
{
mixin("record."~structNames[cnt]) = cast(int)jsonIn[structNames[cnt]].integer();
}
else static if(__traits(isSame, mixin(structTypes[cnt]), uint))
else static if(__traits(isSame, structTypes[cnt], uint))
{
mixin("record."~structNames[cnt]) = cast(uint)jsonIn[structNames[cnt]].uinteger();
}
else static if(__traits(isSame, mixin(structTypes[cnt]), ulong))
else static if(__traits(isSame, structTypes[cnt], ulong))
{
mixin("record."~structNames[cnt]) = cast(ulong)jsonIn[structNames[cnt]].uinteger();
}
else static if(__traits(isSame, mixin(structTypes[cnt]), long))
else static if(__traits(isSame, structTypes[cnt], long))
{
mixin("record."~structNames[cnt]) = cast(long)jsonIn[structNames[cnt]].integer();
}
else static if(__traits(isSame, mixin(structTypes[cnt]), string))
else static if(__traits(isSame, structTypes[cnt], string))
{
mixin("record."~structNames[cnt]) = jsonIn[structNames[cnt]].str();
@ -83,7 +86,7 @@ public RecordType fromJSON(RecordType)(JSONValue jsonIn)
pragma(msg,"record."~structNames[cnt]);
}
}
else static if(__traits(isSame, mixin(structTypes[cnt]), JSONValue))
else static if(__traits(isSame, structTypes[cnt], JSONValue))
{
mixin("record."~structNames[cnt]) = jsonIn[structNames[cnt]];
@ -92,7 +95,7 @@ public RecordType fromJSON(RecordType)(JSONValue jsonIn)
pragma(msg,"record."~structNames[cnt]);
}
}
else static if(__traits(isSame, mixin(structTypes[cnt]), bool))
else static if(__traits(isSame, structTypes[cnt], bool))
{
mixin("record."~structNames[cnt]) = jsonIn[structNames[cnt]].boolean();
@ -101,16 +104,103 @@ public RecordType fromJSON(RecordType)(JSONValue jsonIn)
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])[]))
else static if(isArray!(structTypes[cnt]))
{
mixin("record."~structNames[cnt]) = jsonIn[structNames[cnt]].boolean();
alias recordArrayComponent = mixin("record."~structNames[cnt]);
JSONValue[] jsonArray = jsonIn[structNames[cnt]].array();
for(ulong i = 0; i < jsonArray.length; i++)
{
JSONValue jsonVal = jsonArray[i];
static if(__traits(isSame, ForeachType!(structTypes[cnt]), byte))
{
mixin("record."~structNames[cnt])~= cast(byte)jsonVal.integer();
}
else static if(__traits(isSame, ForeachType!(structTypes[cnt]), ubyte))
{
mixin("record."~structNames[cnt])~= cast(ubyte)jsonVal.uinteger();
}
static if(__traits(isSame, ForeachType!(structTypes[cnt]), short))
{
mixin("record."~structNames[cnt])~= cast(short)jsonVal.integer();
}
else static if(__traits(isSame, ForeachType!(structTypes[cnt]), ushort))
{
mixin("record."~structNames[cnt])~= cast(ushort)jsonVal.uinteger();
}
static if(__traits(isSame, ForeachType!(structTypes[cnt]), int))
{
mixin("record."~structNames[cnt])~= cast(int)jsonVal.integer();
}
else static if(__traits(isSame, ForeachType!(structTypes[cnt]), uint))
{
mixin("record."~structNames[cnt])~= cast(uint)jsonVal.uinteger();
}
static if(__traits(isSame, ForeachType!(structTypes[cnt]), long))
{
mixin("record."~structNames[cnt])~= cast(long)jsonVal.integer();
}
else static if(__traits(isSame, ForeachType!(structTypes[cnt]), ulong))
{
mixin("record."~structNames[cnt])~= cast(ulong)jsonVal.uinteger();
}
else static if(__traits(isSame, ForeachType!(structTypes[cnt]), bool))
{
mixin("record."~structNames[cnt])~= jsonVal.boolean();
}
else static if(__traits(isSame, ForeachType!(structTypes[cnt]), float))
{
mixin("record."~structNames[cnt])~= cast(float)jsonVal.floating();
}
else static if(__traits(isSame, ForeachType!(structTypes[cnt]), double))
{
mixin("record."~structNames[cnt])~= cast(double)jsonVal.floating();
}
else static if(__traits(isSame, ForeachType!(structTypes[cnt]), string))
{
mixin("record."~structNames[cnt])~= jsonVal.str();
}
}
// mixin("record."~structNames[cnt]) = jsonIn[structNames[cnt]].array();
debug(dbg)
{
pragma(msg,"record."~structNames[cnt]);
}
}
else static if(is(structTypes[cnt] == enum))
{
string enumChoice = jsonIn[structNames[cnt]].str();
alias members = EnumMembers!(structTypes[cnt]);
version(dbg)
{
import std.stdio : writeln;
}
static foreach(member; members)
{
version(dbg)
{
writeln(member);
writeln(fullyQualifiedName!(member));
writeln(__traits(identifier, member));
}
if(__traits(identifier, member) == enumChoice)
{
mixin("record."~structNames[cnt]) = member;
}
}
}
else
{
// throw new
@ -118,22 +208,33 @@ public RecordType fromJSON(RecordType)(JSONValue jsonIn)
debug(dbg)
{
pragma(msg, "Unknown type for de-serialization");
pragma(msg, is(structTypes[cnt] == enum));
}
}
}
catch(JSONException e)
{
throw new SerializationError();
throw new DeserializationError();
}
}
return record;
}
/**
* Example deserialization of JSON
* to our `Person` struct
*/
unittest
{
import std.string : cmp;
import std.stdio : writeln;
enum EnumType
{
DOG,
CAT
}
struct Person
{
@ -142,6 +243,11 @@ unittest
public bool isMale;
public JSONValue obj;
public int[] list;
public bool[] list2;
public float[] list3;
public double[] list4;
public string[] list5;
public EnumType animal;
}
JSONValue json = parseJSON(`{
@ -150,7 +256,12 @@ unittest
"age": 23,
"obj" : {"bruh":1},
"isMale": true,
"list": [1,2,3]
"list": [1,2,3],
"list2": [true, false],
"list3": [1.5, 1.4],
"list4": [1.5, 1.4],
"list5": ["baba", "booey"],
"animal": "CAT"
}
`);
@ -158,7 +269,7 @@ unittest
debug(dbg)
{
writeln(person);
writeln("Deserialized as: ", person);
}
assert(cmp(person.firstname, "Tristan") == 0);
@ -166,14 +277,30 @@ unittest
assert(person.age == 23);
assert(person.isMale == true);
assert(person.obj["bruh"].integer() == 1);
//TODO: list test case
assert(person.list == [1,2,3]);
assert(person.list2 == [true, false]);
assert(person.list3 == [1.5F, 1.4F]);
assert(person.list4 == [1.5, 1.4]);
assert(person.list5 == ["baba", "booey"]);
assert(person.animal == EnumType.CAT);
}
unittest
version(unittest)
{
import std.string : cmp;
import std.stdio : writeln;
}
/**
* Another example deserialization of JSON
* to our `Person` struct but here there
* is a problem with deserialization as
* there is a missing field `isMale`
* in the provided JSON
*/
unittest
{
struct Person
{
public string firstname, lastname;
@ -181,6 +308,7 @@ unittest
public bool isMale;
public JSONValue obj;
public int[] list;
}
JSONValue json = parseJSON(`{
@ -197,9 +325,47 @@ unittest
Person person = fromJSON!(Person)(json);
assert(false);
}
catch(SerializationError)
catch(DeserializationError)
{
assert(true);
}
}
unittest
{
enum EnumType
{
DOG,
CAT
}
struct Person
{
public string firstname, lastname;
public EnumType animal;
}
JSONValue json = parseJSON(`{
"firstname" : "Tristan",
"lastname": "Kildaire",
"animal" : "CAT"
}
`);
try
{
Person person = fromJSON!(Person)(json);
writeln(person);
assert(true);
}
catch(DeserializationError)
{
assert(false);
}
}

View File

@ -1,5 +1,11 @@
/**
* Exception types
*/
module jstruct.exceptions;
/**
* General exception type
*/
public abstract class JStructException : Exception
{
this(string msg)
@ -8,10 +14,24 @@ public abstract class JStructException : Exception
}
}
/**
* Error on serialization
*/
public final class SerializationError : JStructException
{
this()
{
super("Errro serializing");
super("Error serializing");
}
}
/**
* Error on deserialization
*/
public final class DeserializationError : JStructException
{
this()
{
super("Error deserializing");
}
}

View File

@ -1,4 +1,19 @@
/**
* Struct JSON serializer/deserializer
*/
module jstruct;
/**
* Serialization utilities
*/
public import jstruct.serializer;
public import jstruct.deserializer;
/**
* Deserialization utilities
*/
public import jstruct.deserializer;
/**
* Exception types
*/
public import jstruct.exceptions;

View File

@ -1,9 +1,20 @@
/**
* Serialization utilities
*/
module jstruct.serializer;
import std.json;
import std.conv : to;
import std.traits : FieldTypeTuple, FieldNameTuple;
import std.traits : isArray;
/**
* Serializes the provided record into JSON
*
* Params:
* record = the record to serialize into
* Returns: A `JSONValue` containing the serialized record
*/
public JSONValue serializeRecord(RecordType)(RecordType record)
{
// Final JSON to submit
@ -23,32 +34,35 @@ public JSONValue serializeRecord(RecordType)(RecordType record)
// pragma(msg, structValues[cnt]);
}
static if(__traits(isSame, mixin(structTypes[cnt]), int))
static if(__traits(isSame, structTypes[cnt], int))
{
builtJSON[structNames[cnt]] = structValues[cnt];
}
else static if(__traits(isSame, mixin(structTypes[cnt]), uint))
else static if(__traits(isSame, structTypes[cnt], uint))
{
builtJSON[structNames[cnt]] = structValues[cnt];
}
else static if(__traits(isSame, mixin(structTypes[cnt]), ulong))
else static if(__traits(isSame, structTypes[cnt], ulong))
{
builtJSON[structNames[cnt]] = structValues[cnt];
}
else static if(__traits(isSame, mixin(structTypes[cnt]), long))
else static if(__traits(isSame, structTypes[cnt], long))
{
builtJSON[structNames[cnt]] = structValues[cnt];
}
else static if(__traits(isSame, mixin(structTypes[cnt]), string))
else static if(__traits(isSame, structTypes[cnt], string))
{
builtJSON[structNames[cnt]] = structValues[cnt];
}
else static if(__traits(isSame, mixin(structTypes[cnt]), JSONValue))
else static if(__traits(isSame, structTypes[cnt], JSONValue))
{
builtJSON[structNames[cnt]] = structValues[cnt];
}
else static if(__traits(isSame, mixin(structTypes[cnt]), bool))
else static if(__traits(isSame, structTypes[cnt], bool))
{
builtJSON[structNames[cnt]] = structValues[cnt];
}
else static if(isArray!(structTypes[cnt]))
{
builtJSON[structNames[cnt]] = structValues[cnt];
}
@ -67,17 +81,27 @@ public JSONValue serializeRecord(RecordType)(RecordType record)
}
// Test serialization of a struct to JSON
private enum EnumType
{
DOG,
CAT
}
unittest
version(unittest)
{
import std.algorithm.searching : canFind;
import std.string : cmp;
import std.stdio : writeln;
}
/**
* Example serialization of our struct
* `Person` to JSON
*/
unittest
{
enum EnumType
{
DOG,
CAT
}
struct Person
{
public string firstname, lastname;
@ -102,6 +126,14 @@ unittest
assert(canFind(keys, "lastname") && cmp(serialized["lastname"].str(), "Kildaire") == 0);
assert(canFind(keys, "age") && serialized["age"].integer() == 23);
assert(canFind(keys, "list"));
JSONValue[] elems = serialized["list"].array();
for(ulong i = 0; i < elems.length; i++)
{
string curElem = elems[i].str();
assert(curElem == p1.list[i]);
}
debug(dbg)
{
writeln(serialized.toPrettyString());