1 module siryul.json; 2 private import siryul.common; 3 private import std.json : JSONValue, JSON_TYPE, parseJSON, toJSON; 4 private import std.range.primitives : ElementType, isInfinite, isInputRange; 5 private import std.traits : isSomeChar; 6 private import std.typecons; 7 /++ 8 + JSON (JavaScript Object Notation) serialization format 9 + 10 + Note that only strings are supported for associative array keys in this format. 11 +/ 12 struct JSON { 13 private import std.meta : AliasSeq; 14 package alias types = AliasSeq!".json"; 15 package enum emptyObject = "{}"; 16 package static T parseInput(T, DeSiryulize flags, U)(U data) if (isInputRange!U && isSomeChar!(ElementType!U)) { 17 return fromJSON!(T,BitFlags!DeSiryulize(flags))(parseJSON(data), T.stringof); 18 } 19 package static string asString(Siryulize flags, T)(T data) { 20 const json = data.toJSON!(BitFlags!Siryulize(flags)); 21 return toJSON(json, true); 22 } 23 } 24 25 private T fromJSON(T, BitFlags!DeSiryulize flags)(JSONValue node, string path = "") if (!isInfinite!T) { 26 import std.conv : text, to; 27 import std.datetime : Date, DateTime, SysTime, TimeOfDay; 28 import std.exception : enforce; 29 import std.meta : AliasSeq; 30 import std.range.primitives : front; 31 import std.range : enumerate, isOutputRange; 32 import std.traits : arity, FieldNameTuple, ForeachType, getUDAs, hasIndirections, hasUDA, isAssociativeArray, isFloatingPoint, isIntegral, isPointer, isSomeChar, isSomeString, isStaticArray, OriginalType, Parameters, PointerTarget, TemplateArgsOf, ValueType; 33 import std.utf : byCodeUnit; 34 static if (is(T == struct) && hasDeserializationMethod!T) { 35 return deserializationMethod!T(fromJSON!(Parameters!(deserializationMethod!T), flags)(node, path)); 36 } else static if (is(T == enum)) { 37 import std.conv : to; 38 if (node.type == JSON_TYPE.STRING) 39 return node.str.to!T; 40 else 41 return fromJSON!(OriginalType!T, flags)(node, path).to!T; 42 } else static if (isIntegral!T) { 43 expect(node, JSON_TYPE.INTEGER, JSON_TYPE.STRING); 44 if (node.type == JSON_TYPE.STRING) 45 return node.str.to!T; 46 return node.integer.to!T; 47 } else static if (isNullable!T) { 48 T output; 49 if (node.type == JSON_TYPE.NULL) 50 output.nullify(); 51 else 52 output = fromJSON!(typeof(output.get), flags)(node, path); 53 return output; 54 } else static if (isFloatingPoint!T) { 55 expect(node, JSON_TYPE.FLOAT, JSON_TYPE.INTEGER, JSON_TYPE.STRING); 56 if (node.type == JSON_TYPE.STRING) { 57 return node.str.to!T; 58 } 59 if (node.type == JSON_TYPE.INTEGER) { 60 return node.integer.to!T; 61 } 62 return node.floating.to!T; 63 } else static if (isSomeString!T) { 64 expect(node, JSON_TYPE.STRING, JSON_TYPE.INTEGER, JSON_TYPE.NULL); 65 if (node.type == JSON_TYPE.INTEGER) 66 return node.integer.to!T; 67 if (node.type == JSON_TYPE.NULL) 68 return T.init; 69 return node.str.to!T; 70 } else static if (isSomeChar!T) { 71 expect(node, JSON_TYPE.STRING, JSON_TYPE.NULL); 72 if (node.type == JSON_TYPE.NULL) 73 return T.init; 74 return node.str.front.to!T; 75 } else static if (is(T == SysTime) || is(T == DateTime) || is(T == Date) || is(T == TimeOfDay)) { 76 return T.fromISOExtString(fromJSON!(string, flags)(node, path)); 77 } else static if (is(T == struct) || (isPointer!T && is(PointerTarget!T == struct))) { 78 expect(node, JSON_TYPE.OBJECT); 79 T output; 80 static if (isPointer!T) { 81 output = new PointerTarget!T; 82 alias Undecorated = PointerTarget!T; 83 } else { 84 alias Undecorated = T; 85 } 86 foreach (member; FieldNameTuple!Undecorated) { 87 static if (__traits(getProtection, __traits(getMember, Undecorated, member)) == "public") { 88 debug string newPath = path~"."~member; 89 else string newPath = ""; 90 alias field = AliasSeq!(__traits(getMember, Undecorated, member)); 91 enum memberName = getMemberName!field; 92 static if ((hasUDA!(field, Optional) || (!!(flags & DeSiryulize.optionalByDefault))) || hasIndirections!(typeof(field))) { 93 if ((memberName !in node.object) || (node.object[memberName].type == JSON_TYPE.NULL)) 94 continue; 95 } else { 96 enforce!JSONDException(memberName in node.object, "Missing non-@Optional "~memberName~" in node"); 97 } 98 alias fromFunc = getConvertFromFunc!(T, field); 99 try { 100 static if (hasUDA!(field, IgnoreErrors)) { 101 try { 102 __traits(getMember, output, member) = fromFunc(fromJSON!(Parameters!(fromFunc)[0], flags)(node[memberName], newPath)); 103 } catch (UnexpectedTypeException) {} //just skip it 104 } else { 105 __traits(getMember, output, member) = fromFunc(fromJSON!(Parameters!(fromFunc)[0], flags)(node[memberName], newPath)); 106 } 107 } catch (Exception e) { 108 e.msg = "Error deserializing "~newPath~": "~e.msg; 109 throw e; 110 } 111 } 112 } 113 return output; 114 } else static if(isOutputRange!(T, ElementType!T)) { 115 import std.algorithm : copy, map; 116 expect(node, JSON_TYPE.ARRAY); 117 T output = new T(node.array.length); 118 copy(node.array.map!(x => fromJSON!(ElementType!T, flags)(x, path)), output); 119 return output; 120 } else static if (isStaticArray!T && isSomeChar!(ElementType!T)) { 121 expect(node, JSON_TYPE.STRING); 122 T output; 123 foreach (i, chr; fromJSON!((ForeachType!T)[], flags)(node, path).byCodeUnit.enumerate(0)) 124 output[i] = chr; 125 return output; 126 } else static if(isStaticArray!T) { 127 expect(node, JSON_TYPE.ARRAY); 128 enforce!JSONDException(node.array.length == T.length, "Static array length mismatch"); 129 T output; 130 foreach (i, JSONValue newNode; node.array) 131 output[i] = fromJSON!(ForeachType!T, flags)(newNode, path); 132 return output; 133 } else static if(isAssociativeArray!T) { 134 expect(node, JSON_TYPE.OBJECT); 135 T output; 136 foreach (string key, JSONValue value; node.object) 137 output[key] = fromJSON!(ValueType!T, flags)(value, path); 138 return output; 139 } else static if (is(T == bool)) { 140 expect(node, JSON_TYPE.TRUE, JSON_TYPE.FALSE); 141 if (node.type == JSON_TYPE.TRUE) 142 return true; 143 else if (node.type == JSON_TYPE.FALSE) 144 return false; 145 assert(false); 146 } else 147 static assert(false, "Cannot read type "~T.stringof~" from JSON"); //unreachable, hopefully. 148 } 149 private void expect(T...)(JSONValue node, T types) { 150 import std.algorithm : among; 151 import std.exception : enforce; 152 enforce(node.type.among(types), new UnexpectedTypeException(types[0], node.type)); 153 } 154 private @property JSONValue toJSON(BitFlags!Siryulize flags, T)(T type) if (!isInfinite!T) { 155 import std.conv : text, to; 156 import std.meta : AliasSeq; 157 import std.range : isInputRange; 158 import std.traits : arity, FieldNameTuple, getSymbolsByUDA, getUDAs, hasUDA, isArray, isAssociativeArray, isPointer, isSomeChar, isSomeString, isStaticArray, PointerTarget, Unqual; 159 JSONValue output; 160 static if (isPointer!T) { 161 alias Undecorated = Unqual!(PointerTarget!T); 162 } else { 163 alias Undecorated = Unqual!T; 164 } 165 static if (is(T == struct) && hasSerializationMethod!T) { 166 output = toJSON!flags(mixin("type."~__traits(identifier, serializationMethod!T))); 167 } else static if (hasUDA!(type, AsString) || is(Undecorated == enum)) { 168 output = JSONValue(type.text); 169 } else static if (isNullable!Undecorated) { 170 if (type.isNull && !(flags & Siryulize.omitNulls)) { 171 output = JSONValue(); 172 } else { 173 output = type.get().toJSON!flags; 174 } 175 } else static if (isTimeType!Undecorated) { 176 output = JSONValue(type.toISOExtString()); 177 } else static if (canStoreUnchanged!Undecorated) { 178 output = JSONValue(type.to!Undecorated); 179 } else static if (isSomeString!Undecorated || (isStaticArray!Undecorated && isSomeChar!(ElementType!Undecorated))) { 180 import std.utf : toUTF8; 181 output = JSONValue(type[].toUTF8); 182 } else static if (isSomeChar!Undecorated) { 183 output = [type].idup.toJSON!flags; 184 } else static if (isAssociativeArray!Undecorated) { 185 string[string] arr; 186 output = JSONValue(arr); 187 foreach (key, value; type) { 188 output.object[key.text] = value.toJSON!flags; 189 } 190 } else static if (isSimpleList!Undecorated) { 191 string[] arr; 192 output = JSONValue(arr); 193 foreach (value; type) { 194 output.array ~= value.toJSON!flags; 195 } 196 } else static if (is(Undecorated == struct)) { 197 string[string] arr; 198 output = JSONValue(arr); 199 foreach (member; FieldNameTuple!Undecorated) { 200 static if (__traits(getProtection, __traits(getMember, Undecorated, member)) == "public") { 201 static if (!!(flags & Siryulize.omitInits)) { 202 static if (isNullable!(typeof(__traits(getMember, T, member)))) { 203 if (__traits(getMember, type, member).isNull) { 204 continue; 205 } 206 } else { 207 if (__traits(getMember, type, member) == __traits(getMember, type, member).init) { 208 continue; 209 } 210 } 211 } 212 enum memberName = getMemberName!(__traits(getMember, Undecorated, member)); 213 output.object[memberName] = getConvertToFunc!(T, __traits(getMember, Undecorated, member))(mixin("type."~member)).toJSON!flags; 214 } 215 } 216 } else { 217 static assert(false, "Cannot write type "~T.stringof~" to JSON"); //unreachable, hopefully 218 } 219 return output; 220 } 221 private template isTimeType(T) { 222 import std.datetime : DateTime, Date, SysTime, TimeOfDay; 223 enum isTimeType = is(T == SysTime) || is(T == DateTime) || is(T == Date) || is(T == TimeOfDay); 224 } 225 private template canStoreUnchanged(T) { 226 import std.traits : isFloatingPoint, isIntegral; 227 enum canStoreUnchanged = isIntegral!T || is(T == string) || is(T == bool) || isFloatingPoint!T; 228 } 229 /++ 230 + Thrown on JSON deserialization errors 231 +/ 232 class JSONDException : DeserializeException { 233 this(string msg, string file = __FILE__, size_t line = __LINE__) @safe pure nothrow { 234 super(msg, file, line); 235 } 236 } 237 /++ 238 + Thrown when a JSON value has an unexpected type. 239 +/ 240 class UnexpectedTypeException : JSONDException { 241 package this(JSON_TYPE expectedType, JSON_TYPE unexpectedType, string file = __FILE__, size_t line = __LINE__) @safe pure nothrow { 242 import std.conv : text; 243 import std.exception : assumeWontThrow, ifThrown; 244 super("Expecting JSON type "~assumeWontThrow(expectedType.text.ifThrown("Unknown"))~", got "~assumeWontThrow(unexpectedType.text.ifThrown("Unknown")), file, line); 245 } 246 }