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 }