1 module dash.utility.data.serialization; 2 import dash.utility.data.yaml; 3 import dash.utility.resources, dash.utility.math, dash.utility.output; 4 5 import vibe.data.json, vibe.data.bson; 6 import std.typecons: Tuple, tuple; 7 import std.typetuple; 8 import std.math; 9 10 // Serialization attributes 11 public import vibe.data.serialization: rename = name, asArray, byName, ignore, optional, isCustomSerializable; 12 13 /** 14 * Modes of serialization. 15 */ 16 enum SerializationMode 17 { 18 Default, 19 Json, 20 Bson, 21 Yaml, 22 } 23 24 /** 25 * Deserializes a file. 26 * 27 * Params: 28 * fileName = The name of the file to deserialize. 29 * 30 * Returns: The deserialized object. 31 */ 32 Tuple!( T, Resource ) deserializeFileByName( T )( string fileName, SerializationMode mode = SerializationMode.Default ) 33 { 34 import std.path: dirName, baseName; 35 import std.array: empty, front; 36 37 auto files = fileName.dirName.scanDirectory( fileName.baseName ~ ".*" ); 38 return files.empty 39 ? tuple( T.init, internalResource ) 40 : tuple( deserializeFile!T( files.front ), files.front ); 41 } 42 43 /** 44 * Deserializes a file. 45 * 46 * Params: 47 * file = The name of the file to deserialize. 48 * 49 * Returns: The deserialized object. 50 */ 51 T deserializeFile( T )( Resource file, SerializationMode mode = SerializationMode.Default ) 52 { 53 import std.path: extension; 54 import std..string: toLower; 55 56 T handleJson() 57 { 58 return deserializeJson!T( file.readText().parseJsonString() ); 59 } 60 61 T handleBson() 62 { 63 return deserializeBson!T( Bson( Bson.Type.object, file.read().idup ) ); 64 } 65 66 T handleYaml() 67 { 68 return deserializeYaml!T( Loader( file.fullPath ).load() ); 69 } 70 71 try 72 { 73 final switch( mode ) with( SerializationMode ) 74 { 75 case Json: return handleJson(); 76 case Bson: return handleBson(); 77 case Yaml: return handleYaml(); 78 case Default: 79 switch( file.extension.toLower ) 80 { 81 case ".json": return handleJson(); 82 case ".bson": return handleBson(); 83 case ".yaml": 84 case ".yml": return handleYaml(); 85 default: throw new Exception( "File extension " ~ file.extension.toLower ~ " not supported." ); 86 } 87 } 88 } 89 catch( Exception e ) 90 { 91 errorf( "Error deserializing file %s to type %s: %s", file.fileName, T.stringof, e.msg ); 92 return T.init; 93 } 94 } 95 96 /** 97 * Deserializes a file with multiple documents. 98 * 99 * Params: 100 * file = The name of the file to deserialize. 101 * 102 * Returns: The deserialized object. 103 */ 104 T[] deserializeMultiFile( T )( Resource file, SerializationMode mode = SerializationMode.Default ) 105 { 106 import std.path: extension; 107 import std..string: toLower; 108 109 T[] handleJson() 110 { 111 return [deserializeFile!T( file, SerializationMode.Json )]; 112 } 113 114 T[] handleBson() 115 { 116 return [deserializeFile!T( file, SerializationMode.Bson )]; 117 } 118 119 T[] handleYaml() 120 { 121 import std.algorithm: map; 122 import std.array: array; 123 return Loader( file.fullPath ) 124 .loadAll() 125 .map!( node => node.deserializeYaml!T() ) 126 .array(); 127 } 128 129 try 130 { 131 final switch( mode ) with( SerializationMode ) 132 { 133 case Json: return handleJson(); 134 case Bson: return handleBson(); 135 case Yaml: return handleYaml(); 136 case Default: 137 switch( file.extension.toLower ) 138 { 139 case ".json": return handleJson(); 140 case ".bson": return handleBson(); 141 case ".yaml": 142 case ".yml": return handleYaml(); 143 default: throw new Exception( "File extension " ~ file.extension.toLower ~ " not supported." ); 144 } 145 } 146 } 147 catch( Exception e ) 148 { 149 errorf( "Error deserializing file %s to type %s: %s", file.fileName, T.stringof, e.msg ); 150 return []; 151 } 152 } 153 154 /** 155 * Serializes an object to a file. 156 */ 157 template serializeToFile( bool prettyPrint = true ) 158 { 159 void serializeToFile( T )( T t, string outPath, SerializationMode mode = SerializationMode.Default ) 160 { 161 import std.path: extension; 162 import std..string: toLower; 163 import std.file: write; 164 import std.array: appender; 165 166 void handleJson() 167 { 168 auto json = appender!string; 169 writeJsonString!( typeof(json), prettyPrint )( json, serializeToJson( t ) ); 170 write( outPath, json.data ); 171 } 172 173 void handleBson() 174 { 175 write( outPath, serializeToBson( t ).data ); 176 } 177 178 void handleYaml() 179 { 180 Dumper( outPath ).dump( serializeToYaml( t ) ); 181 } 182 183 try 184 { 185 final switch( mode ) with( SerializationMode ) 186 { 187 case Json: handleJson(); break; 188 case Bson: handleBson(); break; 189 case Yaml: handleYaml(); break; 190 case Default: 191 switch( outPath.extension.toLower ) 192 { 193 case ".json": handleJson(); break; 194 case ".bson": handleBson(); break; 195 case ".yaml": 196 case ".yml": handleYaml(); break; 197 default: throw new Exception( "File extension " ~ outPath.extension.toLower ~ " not supported." ); 198 } 199 break; 200 } 201 } 202 catch( Exception e ) 203 { 204 errorf( "Error serializing %s to file %s: %s", T.stringof, file.fileName, e.msg ); 205 } 206 } 207 } 208 209 /// Supported serialization formats. 210 enum serializationFormats = tuple( "Json", "Bson", "Yaml" ); 211 212 /// Type to use when defining custom 213 struct CustomSerializer( _T, _Rep, alias _ser, alias _deser, alias _check ) 214 if( is( typeof( _ser( _T.init ) ) == _Rep ) && 215 is( typeof( _deser( _Rep.init ) ) == _T ) && 216 is( typeof( _check( _Rep.init ) ) == bool ) ) 217 { 218 /// The type being serialized 219 alias T = _T; 220 /// The serialized representation 221 alias Rep = _Rep; 222 /// Function to convert the type to its rep 223 alias serialize = _ser; 224 /// Function to convert the rep to the type 225 alias deserialize = _deser; 226 /// Function called to ensure the representation is valid 227 alias check = _check; 228 } 229 230 /// For calling templated templates. 231 template PApply( alias Target, T... ) 232 { 233 alias PApply( U... ) = Target!( T, U ); 234 } 235 236 /// Checks if a serializer is for a type. 237 enum serializerTypesMatch( Type, alias CS ) = is( CS.T == Type ); 238 239 /// Predicate for std.typetupple 240 alias isSerializerFor( T ) = PApply!( serializerTypesMatch, T ); 241 242 /// Does a given type have a serializer 243 enum hasSerializer( T ) = anySatisfy!( isSerializerFor!T, customSerializers ); 244 245 /// Get the serializer for a type 246 template serializerFor( T ) 247 { 248 static if( hasSerializer!T ) 249 alias serializerFor = Filter!( isSerializerFor!T, customSerializers )[ 0 ]; 250 else 251 alias serializerFor = defaultSerializer!T; 252 } 253 254 /// A tuple of all supported serializers 255 alias customSerializers = TypeTuple!( 256 CustomSerializer!( vec2f, float[], vec => vec.vector[], arr => vec2f( arr ), arr => arr.length == 2 ), 257 CustomSerializer!( vec3f, float[], vec => vec.vector[], arr => vec3f( arr ), arr => arr.length == 3 ), 258 CustomSerializer!( quatf, float[], vec => vec.toEulerAngles.vector[], arr => fromEulerAngles( arr ), arr => arr.length == 3 ), 259 ); 260 static assert( hasSerializer!vec2f ); 261 static assert( hasSerializer!vec3f ); 262 static assert( hasSerializer!quatf ); 263 264 /// Serializer for all other types 265 alias defaultSerializer( T ) = CustomSerializer!( T, T, t => t, t => t, t => true );