1 /** 2 * JSON-based provider 3 * 4 * You should use this when 5 * you want to fill up your 6 * config with values stored 7 * in a string containing 8 * JSON-encoded data 9 * 10 * Authors: Tristan Brice Velloza Kildaire (deavmi) 11 */ 12 module hummus.providers.json; 13 14 import hummus.provider : Provider; 15 import std.json : JSONValue, JSONType; 16 import niknaks.json : traverseTo; 17 18 version(unittest) 19 { 20 import gogga.mixins; 21 } 22 23 /** 24 * A provider which will look for 25 * JSON key-value pairs based on 26 * matching them to the names of the 27 * fields in the provided struct. 28 * 29 * Struct fields which are of 30 * a struct-type themselves are 31 * supported and are filled 32 * whenever json such as `x.y` 33 * is encountered. This means `x` 34 * is some field in the "outer" 35 * struct. Then because we have 36 * `x.y`, `x` MUST be of a struct 37 * type. Then we access the field 38 * named `y` in this "inner" struct 39 */ 40 public class JSONProvider : Provider 41 { 42 import std.json : parseJSON, JSONException; 43 44 private JSONValue _j; 45 46 /** 47 * Constructs a new JSON provider 48 * with the given input JSON to 49 * parse 50 * 51 * Params: 52 * json = the input JSON 53 * Throws: 54 * JSONException when parsing 55 * fails 56 */ 57 this(string json) 58 { 59 // todo: handle exceptions in non-library specific way 60 // OR require they parse in the JSONValue - that is 61 // library-dependent tho 62 this._j = parseJSON(json); 63 } 64 65 /** 66 * Implementation 67 */ 68 protected bool provideImpl(string n, ref string v) 69 { 70 // todo: check return value for nullity 71 JSONValue* f_node = traverseTo(n, &this._j); 72 73 // todo: value conversion here 74 if(f_node is null) 75 { 76 return false; 77 } 78 79 string s_out; 80 if(jsonNormal(f_node, s_out)) 81 { 82 version(unittest) 83 DEBUG("found JSON node toString(): ", s_out); 84 85 v = s_out; 86 return true; 87 } 88 else 89 { 90 return false; 91 } 92 } 93 } 94 95 private bool jsonNormal(JSONValue* i, ref string o) 96 { 97 auto t = i.type(); 98 if(t == JSONType..string) 99 { 100 o = i.str(); 101 return true; 102 } 103 else if(t == JSONType.ARRAY) 104 { 105 version(unittest) 106 DEBUG("'", i, "' is an array type, these are unsupported"); 107 108 return false; 109 } 110 // todo: disallow array types and object types 111 else 112 { 113 o = i.toString(); 114 return true; 115 } 116 } 117 118 private version(unittest) 119 { 120 import hummus.cfg : fieldsOf; 121 import std.stdio : writeln; 122 } 123 124 unittest 125 { 126 struct Inner 127 { 128 int prop; 129 int k; 130 } 131 132 struct Basic 133 { 134 string name; 135 ulong age; 136 Inner x; 137 string bad; 138 } 139 140 auto cfg = Basic(); 141 writeln("Before provisioning: ", cfg); 142 143 // input json 144 string json = ` 145 { 146 "name": "Tristan Brice Velloza Kildaire", 147 "age": 25, 148 "x": { 149 "prop": 2 150 }, 151 "bad": ["", 2] 152 } 153 `; 154 155 // create a new JSON provider with the 156 // input JSON 157 fieldsOf(cfg, new JSONProvider(json)); 158 159 assert(cfg.name == "Tristan Brice Velloza Kildaire"); 160 assert(cfg.age == 25); 161 assert(cfg.x.prop == 2); 162 163 // it is not present (in the JSON) hence it should 164 // never be set in our struct 165 assert(cfg.x.k == cfg.x.k.init); 166 assert(cfg.bad.length == 0); 167 }