1 module google.protobuf.json_decoding; 2 3 import std.json : JSONValue, JSON_TYPE; 4 import std.traits : isArray, isAssociativeArray, isBoolean, isFloatingPoint, isIntegral, isSigned; 5 import google.protobuf.common; 6 7 T fromJSONValue(T)(JSONValue value) 8 if (isBoolean!T) 9 { 10 switch (value.type) 11 { 12 case JSON_TYPE.NULL: 13 return protoDefaultValue!T; 14 case JSON_TYPE.TRUE: 15 return true; 16 case JSON_TYPE.FALSE: 17 return false; 18 default: 19 throw new ProtobufException("JSON boolean expected"); 20 } 21 } 22 23 T fromJSONValue(T)(JSONValue value) 24 if (isIntegral!T) 25 { 26 import std.conv : ConvException, to; 27 import std.exception : enforce; 28 import std.traits : OriginalType; 29 30 try 31 { 32 switch (value.type) 33 { 34 case JSON_TYPE.NULL: 35 return protoDefaultValue!T; 36 case JSON_TYPE.STRING: 37 return value.str.to!T; 38 case JSON_TYPE.INTEGER: 39 return cast(T) value.integer.to!(OriginalType!T); 40 case JSON_TYPE.UINTEGER: 41 return cast(T) value.uinteger.to!(OriginalType!T); 42 case JSON_TYPE.FLOAT: 43 { 44 import core.stdc.math : fabs, modf; 45 46 double integral; 47 double fractional = modf(value.floating, &integral); 48 double epsilon = double.epsilon * fabs(integral); 49 50 enforce!ProtobufException(fabs(fractional) <= epsilon, "JSON integer expected"); 51 52 return value.floating.to!T; 53 } 54 default: 55 throw new ProtobufException("JSON integer expected"); 56 } 57 } 58 catch (ConvException ConvException) 59 { 60 throw new ProtobufException("JSON integer expected"); 61 } 62 } 63 64 T fromJSONValue(T)(JSONValue value) 65 if (isFloatingPoint!T) 66 { 67 import std.conv : ConvException, to; 68 import std.math : isInfinity, isNaN; 69 70 try 71 { 72 switch (value.type) 73 { 74 case JSON_TYPE.NULL: 75 return protoDefaultValue!T; 76 case JSON_TYPE.STRING: 77 switch (value.str) 78 { 79 case "NaN": 80 return T.nan; 81 case "Infinity": 82 return T.infinity; 83 case "-Infinity": 84 return -T.infinity; 85 default: 86 return value.str.to!T; 87 } 88 case JSON_TYPE.INTEGER: 89 return value.integer.to!T; 90 case JSON_TYPE.UINTEGER: 91 return value.uinteger.to!T; 92 case JSON_TYPE.FLOAT: 93 return value.floating; 94 default: 95 throw new ProtobufException("JSON float expected"); 96 } 97 } 98 catch (ConvException ConvException) 99 { 100 throw new ProtobufException("JSON float expected"); 101 } 102 } 103 104 T fromJSONValue(T)(JSONValue value) 105 if (is(T == string)) 106 { 107 import std.exception : enforce; 108 109 if (value.isNull) 110 return protoDefaultValue!T; 111 112 enforce!ProtobufException(value.type == JSON_TYPE.STRING, "JSON string expected"); 113 return value.str; 114 } 115 116 T fromJSONValue(T)(JSONValue value) 117 if (is(T == bytes)) 118 { 119 import std.base64 : Base64; 120 import std.exception : enforce; 121 import std.json : JSON_TYPE; 122 123 if (value.isNull) 124 return protoDefaultValue!T; 125 126 enforce!ProtobufException(value.type == JSON_TYPE.STRING, "JSON base64 encoded binary expected"); 127 return Base64.decode(value.str); 128 } 129 130 T fromJSONValue(T)(JSONValue value) 131 if (isArray!T && !is(T == string) && !is(T == bytes)) 132 { 133 import std.algorithm : map; 134 import std.array : array; 135 import std.exception : enforce; 136 import std.range : ElementType; 137 138 if (value.isNull) 139 return protoDefaultValue!T; 140 141 enforce!ProtobufException(value.type == JSON_TYPE.ARRAY, "JSON array expected"); 142 return value.array.map!(a => a.fromJSONValue!(ElementType!T)).array; 143 } 144 145 T fromJSONValue(T)(JSONValue value, T result = null) 146 if (isAssociativeArray!T) 147 { 148 import std.conv : ConvException, to; 149 import std.exception : enforce; 150 import std.traits : KeyType, ValueType; 151 152 if (value.isNull) 153 return protoDefaultValue!T; 154 155 enforce!ProtobufException(value.type == JSON_TYPE.OBJECT, "JSON object expected"); 156 foreach (k, v; value.object) 157 { 158 try 159 { 160 result[k.to!(KeyType!T)] = v.fromJSONValue!(ValueType!T); 161 } 162 catch (ConvException exception) 163 { 164 throw new ProtobufException(exception.msg); 165 } 166 } 167 168 return result; 169 } 170 171 unittest 172 { 173 import std.exception : assertThrown; 174 import std.json : parseJSON; 175 import std.math : isInfinity, isNaN; 176 177 assert(fromJSONValue!bool(JSONValue(false)) == false); 178 assert(fromJSONValue!bool(JSONValue(true)) == true); 179 assertThrown!ProtobufException(fromJSONValue!bool(JSONValue(1))); 180 181 assert(fromJSONValue!int(JSONValue(1)) == 1); 182 assert(fromJSONValue!uint(JSONValue(1U)) == 1U); 183 assert(fromJSONValue!long(JSONValue(1L)) == 1); 184 assert(fromJSONValue!ulong(JSONValue(1UL)) == 1U); 185 assertThrown!ProtobufException(fromJSONValue!int(JSONValue(false))); 186 assertThrown!ProtobufException(fromJSONValue!ulong(JSONValue("foo"))); 187 188 assert(fromJSONValue!float(JSONValue(1.0f)) == 1.0); 189 assert(fromJSONValue!double(JSONValue(1.0)) == 1.0); 190 assert(fromJSONValue!float(JSONValue("NaN")).isNaN); 191 assert(fromJSONValue!double(JSONValue("Infinity")).isInfinity); 192 assert(fromJSONValue!double(JSONValue("-Infinity")).isInfinity); 193 assertThrown!ProtobufException(fromJSONValue!float(JSONValue(false))); 194 assertThrown!ProtobufException(fromJSONValue!double(JSONValue("foo"))); 195 196 assert(fromJSONValue!bytes(JSONValue("Zm9v")) == cast(bytes) "foo"); 197 assertThrown!ProtobufException(fromJSONValue!bytes(JSONValue(1))); 198 199 assert(fromJSONValue!(int[])(parseJSON(`[1, 2, 3]`)) == [1, 2, 3]); 200 assertThrown!ProtobufException(fromJSONValue!(int[])(JSONValue(`[1, 2, 3]`))); 201 202 assert(fromJSONValue!(bool[int])(parseJSON(`{"1": false, "2": true}`)) == [1 : false, 2 : true]); 203 assertThrown!ProtobufException(fromJSONValue!(bool[int])(JSONValue(`{"1": false, "2": true}`))); 204 assertThrown!ProtobufException(fromJSONValue!(bool[int])(parseJSON(`{"foo": false, "2": true}`))); 205 } 206 207 T fromJSONValue(T)(JSONValue value, T result = protoDefaultValue!T) 208 if (is(T == class) || is(T == struct)) 209 { 210 import std.algorithm : findAmong; 211 import std.exception : enforce; 212 import std.meta : staticMap; 213 import std.range : empty; 214 import std.traits : hasMember; 215 216 static if (is(T == class)) 217 { 218 if (result is null) 219 result = new T; 220 } 221 222 static if (hasMember!(T, "fromJSONValue")) 223 { 224 return result.fromJSONValue(value); 225 } 226 else 227 { 228 enum jsonName(string fieldName) = { 229 import std.algorithm : skipOver; 230 231 string result = fieldName; 232 233 if (fieldName[$ - 1] == '_') 234 result = fieldName[0 .. $ - 1]; 235 236 static if (isOneof!(mixin("T." ~ fieldName))) 237 result.skipOver("_"); 238 239 return result; 240 }(); 241 242 if (value.isNull) 243 return protoDefaultValue!T; 244 245 enforce!ProtobufException(value.type == JSON_TYPE.OBJECT, "JSON object expected"); 246 247 JSONValue[string] members = value.object; 248 249 foreach (fieldName; Message!T.fieldNames) 250 { 251 enum jsonFieldName = jsonName!fieldName; 252 253 auto fieldValue = (jsonFieldName in members); 254 if (fieldValue !is null) 255 { 256 static if (isOneof!(mixin("T." ~ fieldName))) 257 { 258 alias otherFields = staticMap!(jsonName, otherOneofFieldNames!(T, mixin("T." ~ fieldName))); 259 enforce!ProtobufException(members.keys.findAmong([otherFields]).empty, 260 "More than one oneof field in JSON Message"); 261 } 262 263 mixin("result." ~ fieldName) = fromJSONValue!(typeof(mixin("T." ~ fieldName)))(*fieldValue); 264 } 265 } 266 return result; 267 } 268 } 269 270 unittest 271 { 272 import std.exception : assertThrown; 273 import std.json : parseJSON; 274 275 struct Foo 276 { 277 @Proto(1) int a; 278 @Proto(3) string b; 279 @Proto(4) bool c; 280 281 @Oneof("test") 282 union 283 { 284 @Proto(5) int _d; 285 @Proto(6) string _e; 286 } 287 } 288 289 auto foo = Foo(10, "abc", false); 290 291 assert(fromJSONValue!Foo(parseJSON(`{"a":10, "b":"abc"}`)) == Foo(10, "abc", false)); 292 assert(fromJSONValue!Foo(parseJSON(`{"a": 10, "b": "abc", "c": false}`)) == Foo(10, "abc", false)); 293 assertThrown!ProtobufException(fromJSONValue!Foo(parseJSON(`{"a":10, "b":100}`))); 294 assertThrown!ProtobufException(fromJSONValue!Foo(parseJSON(`{"d":10, "e":"abc"}`))); 295 } 296 297 unittest 298 { 299 import std.json : parseJSON; 300 301 struct EmptyMessage 302 { 303 } 304 305 assert(fromJSONValue!EmptyMessage(parseJSON(`{}`)) == EmptyMessage()); 306 assert(fromJSONValue!EmptyMessage(parseJSON(`{"a":10, "b":"abc"}`)) == EmptyMessage()); 307 } 308 309 private template oneofs(T) 310 { 311 import std.meta : NoDuplicates, staticMap; 312 import std.traits : getSymbolsByUDA; 313 314 private alias oneofs = NoDuplicates!(staticMap!(oneofByField, getSymbolsByUDA!(T, Oneof))); 315 } 316 317 private template oneofByField(alias field) 318 { 319 import std.traits : getUDAs; 320 321 enum Oneof oneofByField = getUDAs!(field, Oneof)[0]; 322 } 323 324 private template otherOneofFieldNames(T, alias field) 325 { 326 import std.meta : Erase, Filter, staticMap; 327 328 static assert(is(typeof(__traits(parent, field).init) == T)); 329 static assert(isOneof!field); 330 331 static enum hasSameOneofCase(alias field2) = oneofCaseFieldName!field == oneofCaseFieldName!field2; 332 static enum fieldName(alias field) = __traits(identifier, field); 333 334 enum otherOneofFieldNames = staticMap!(fieldName, Erase!(field, Filter!(hasSameOneofCase, Filter!(isOneof, 335 Message!T.fields)))); 336 } 337 338 unittest 339 { 340 import std.meta : AliasSeq, staticMap; 341 342 static struct Test 343 { 344 @Oneof("foo") 345 union 346 { 347 @Proto(1) int foo1; 348 @Proto(2) int foo2; 349 } 350 @Oneof("bar") 351 union 352 { 353 @Proto(11) int bar1; 354 @Proto(12) int bar2; 355 @Proto(13) int bar3; 356 } 357 358 @Proto(20) int baz; 359 } 360 361 static assert([otherOneofFieldNames!(Test, Test.foo1)] == ["foo2"]); 362 static assert([otherOneofFieldNames!(Test, Test.bar2)] == ["bar1", "bar3"]); 363 }