1 module google.protobuf.json_encoding; 2 3 import std.json : JSONValue; 4 import std.traits : isArray, isAssociativeArray, isBoolean, isFloatingPoint, isIntegral, isSigned; 5 import google.protobuf.common; 6 7 JSONValue toJSONValue(T)(T value) 8 if (isBoolean!T || isIntegral!T || is(T == string)) 9 { 10 return JSONValue(value); 11 } 12 13 JSONValue toJSONValue(T)(T value) 14 if (isFloatingPoint!T) 15 { 16 import std.math : isInfinity, isNaN; 17 18 if (value.isNaN) 19 return JSONValue("NaN"); 20 else if (value.isInfinity) 21 return JSONValue(value < 0 ? "-Infinity" : "Infinity"); 22 else 23 return JSONValue(value); 24 } 25 26 JSONValue toJSONValue(T)(T value) 27 if (is(T == bytes)) 28 { 29 import std.base64 : Base64; 30 31 return JSONValue(cast(string) Base64.encode(value)); 32 } 33 34 JSONValue toJSONValue(T)(T value) 35 if (isArray!T && !is(T == string) && !is(T == bytes)) 36 { 37 import std.algorithm : map; 38 import std.array : array; 39 40 return JSONValue(value.map!(a => a.toJSONValue).array); 41 } 42 43 JSONValue toJSONValue(T)(T value) 44 if (isAssociativeArray!T) 45 { 46 import std.conv : to; 47 48 JSONValue[string] members; 49 50 foreach (k, v; value) 51 members[k.to!string] = toJSONValue(v); 52 53 return JSONValue(members); 54 } 55 56 unittest 57 { 58 import std.json : parseJSON, toJSON; 59 60 assert(toJSONValue(true) == JSONValue(true)); 61 assert(toJSONValue(false) == JSONValue(false)); 62 assert(toJSONValue(1) == JSONValue(1)); 63 assert(toJSONValue(1U) == JSONValue(1U)); 64 assert(toJSONValue(1L) == JSONValue(1)); 65 assert(toJSONValue(1UL) == JSONValue(1U)); 66 assert(toJSONValue(1.0f) == JSONValue(1.0)); 67 assert(toJSONValue(1.0) == JSONValue(1.0)); 68 69 auto jsonValue = toJSONValue(double.nan); 70 assert(toJSON(jsonValue) ==`"NaN"`); 71 jsonValue = toJSONValue(float.infinity); 72 assert(toJSON(jsonValue) == `"Infinity"`); 73 jsonValue = toJSONValue(-double.infinity); 74 assert(toJSON(jsonValue) == `"-Infinity"`); 75 76 assert(toJSONValue(cast(bytes) "foo") == JSONValue("Zm9v")); 77 assert(toJSONValue([1, 2, 3]) == parseJSON(`[1, 2, 3]`)); 78 assert([1 : false, 2 : true].toJSONValue == parseJSON(`{"1": false, "2": true}`)); 79 } 80 81 JSONValue toJSONValue(T)(T value) 82 if (is(T == class) || is(T == struct)) 83 { 84 import std.meta : AliasSeq; 85 import std.traits : hasMember; 86 87 static if (is(T == class)) 88 { 89 if (value is null) 90 { 91 return JSONValue(null); 92 } 93 } 94 95 static if (hasMember!(T, "toJSONValue")) 96 { 97 return value.toJSONValue; 98 } 99 else 100 { 101 JSONValue[string] members; 102 103 foreach (fieldName; Message!T.fieldNames) 104 { 105 static if (isOneof!(mixin("T." ~ fieldName))) 106 { 107 auto oneofCase = __traits(getMember, value, oneofCaseFieldName!(mixin("T." ~ fieldName))); 108 enum fieldCase = "T." ~ typeof(oneofCase).stringof ~ "." ~ oneofAccessorName!(mixin("T." ~ fieldName)); 109 110 if (oneofCase == mixin(fieldCase)) 111 members[oneofAccessorName!(mixin("T." ~ fieldName))] = mixin("value." ~ fieldName).toJSONValue; 112 } 113 else 114 { 115 if (mixin("value." ~ fieldName) != protoDefaultValue!(typeof(mixin("T." ~ fieldName)))) 116 members[fieldName] = mixin("value." ~ fieldName).toJSONValue; 117 } 118 } 119 120 return JSONValue(members); 121 } 122 } 123 124 unittest 125 { 126 import std.json : parseJSON; 127 128 struct Foo 129 { 130 @Proto(1) int a; 131 @Proto(3) string b; 132 @Proto(4) bool c; 133 } 134 135 auto foo = Foo(10, "abc", false); 136 137 assert(foo.toJSONValue == parseJSON(`{"a":10, "b":"abc"}`)); 138 } 139 140 unittest 141 { 142 import std.json : parseJSON; 143 144 struct EmptyMessage 145 { 146 } 147 148 EmptyMessage emptyMessage; 149 150 assert(emptyMessage.toJSONValue == parseJSON(`{}`)); 151 } 152 153 unittest 154 { 155 import std.json : parseJSON; 156 157 struct Foo 158 { 159 @Proto(1) int a; 160 161 enum MeterOrInchCase 162 { 163 meterOrInchNotSet = 0, 164 meter = 3, 165 inch = 5, 166 } 167 MeterOrInchCase _meterOrInchCase = MeterOrInchCase.meterOrInchNotSet; 168 @property MeterOrInchCase meterOrInchCase() { return _meterOrInchCase; } 169 void clearMeterOrInchCase() { _meterOrInchCase = MeterOrInchCase.meterOrInchNotSet; } 170 @Oneof("_meterOrInchCase") union 171 { 172 @Proto(3) int _meter = protoDefaultValue!int; mixin(oneofAccessors!_meter); 173 @Proto(5) int _inch; mixin(oneofAccessors!_inch); 174 } 175 } 176 177 auto foo = Foo(10); 178 179 assert(foo.toJSONValue == parseJSON(`{"a":10}`)); 180 181 foo.meter = 10; 182 assert(foo.toJSONValue == parseJSON(`{"a":10, "meter":10}`)); 183 184 foo.inch = 20; 185 assert(foo.toJSONValue == parseJSON(`{"a":10, "inch":20}`)); 186 187 foo.meter = 0; 188 assert(foo.toJSONValue == parseJSON(`{"a":10, "meter":0}`)); 189 190 foo.a = 0; 191 assert(foo.toJSONValue == parseJSON(`{"meter":0}`)); 192 193 foo.clearMeterOrInchCase; 194 assert(foo.toJSONValue == parseJSON(`{}`)); 195 }