1 /** 2 JSON Reader 3 4 Copyright: 5 Copyright © 2020-2026, Inochi2D Project 6 7 License: 8 $(LINK2 https://github.com/Inochi2D/inochi2d/blob/main/LICENSE, BSD 2-clause License) 9 10 Authors: 11 Luna Nielsen 12 */ 13 module inp.format.json.reader; 14 import inp.format.parse; 15 import inp.format.node; 16 import nulib.io.stream; 17 import nulib.io.stream.rw; 18 import nulib.string; 19 import nulib.math; 20 import nulib.conv; 21 import numem; 22 23 @nogc: 24 25 /** 26 Reads and parses a JSON string into a $(D DataNode). 27 28 Params: 29 stream = The stream to read. 30 length = The amount of characters to read. 31 32 Returns: 33 A result type containing either a $(D DataNode) 34 or an error message. 35 */ 36 Result!DataNode readJson(Stream stream, size_t length = 0) @nogc { 37 DataNode result; 38 scope StreamReader reader = new StreamReader(stream); 39 if (auto err = reader.readJson(result, length)) 40 return error!DataNode(err); 41 return ok(result.move()); 42 } 43 44 /** 45 Reads JSON data from the given existing stream. 46 47 Params: 48 reader = The stream reader to read from. 49 node = The node to store the result in. 50 length = The length of the buffer to read. 51 52 Returns: 53 Any errors emitted. 54 */ 55 ErrorString readJson(StreamReader reader, ref DataNode node, size_t length) { 56 if (length > 0) { 57 ubyte[] buffer = nu_malloca!ubyte(length); 58 if (reader.stream.read(buffer) < length) 59 return "Reached EOF!"; 60 61 scope MemoryStream mstream = new MemoryStream(buffer); 62 scope StreamReader mreader = new StreamReader(mstream); 63 return mreader.readJson(node); 64 } 65 return reader.readJson(node); 66 } 67 68 69 70 71 // 72 // IMPLEMENTATION DETAILS 73 // 74 private: 75 76 /** 77 Reads a JSON value from the stream. 78 */ 79 ErrorString readJson(StreamReader reader, ref DataNode node) { 80 reader.skipWhitespace(); 81 switch(reader.peekChar()) { 82 default: 83 if (reader.peekString("true")) { 84 reader.skip(4); 85 node = DataNode(true); 86 return null; 87 } 88 89 if (reader.peekString("false")) { 90 reader.skip(5); 91 node = DataNode(false); 92 return null; 93 } 94 95 // Skip 'null' 96 if (reader.peekString("null")) { 97 reader.skip(4); 98 return null; 99 } 100 101 // Okay, no idea what this character is. 102 return "Unexpected token"; 103 104 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': 105 case '+', '-': 106 return reader.readJsonNumber(node); 107 108 case '"': 109 return reader.readJsonString(node); 110 111 case '{': 112 return reader.readJsonObject(node); 113 114 case '[': 115 return reader.readJsonArray(node); 116 } 117 } 118 119 ErrorString readJsonString(StreamReader reader, ref DataNode node) { 120 node = DataNode(reader.readStringJson().take()); 121 return null; 122 } 123 124 ErrorString readJsonNumber(StreamReader reader, ref DataNode node) { 125 bool isFloating; 126 nstring num = reader.readNumberJson(isFloating); 127 128 if (isFloating) 129 node = DataNode(parseFloat!double(num[])); 130 else 131 node = DataNode(parseInt!long(num[])); 132 133 return null; 134 } 135 136 ErrorString readJsonArray(StreamReader reader, ref DataNode node) { 137 char c = cast(char)reader.readU8(); 138 if (c != '[') 139 return "Not an array"; 140 141 node = DataNode.createArray(); 142 143 // Empty array. 144 if (reader.peekChar() == ']') { 145 reader.skip(1); 146 return null; 147 } 148 149 DataNode value; 150 do { 151 152 // Read value 153 reader.skipWhitespace(); 154 if (auto error = reader.readJson(value)) { 155 return error; 156 } 157 reader.skipWhitespace(); 158 159 // Append value 160 node ~= value.move(); 161 162 // Consume comma if needed. 163 c = cast(char)reader.readU8(); 164 if (c == ',') { 165 reader.skipWhitespace(); 166 continue; 167 } 168 169 // Consume array end. 170 if (c == ']') 171 break; 172 173 } while(!reader.eof()); 174 return null; 175 } 176 177 ErrorString readJsonObject(StreamReader reader, ref DataNode node) { 178 char c = cast(char)reader.readU8(); 179 if (c != '{') 180 return "Not an object"; 181 182 node = DataNode.createObject(); 183 184 // Empty object. 185 if (reader.peekChar() == '}') { 186 reader.skip(1); 187 return null; 188 } 189 190 DataNode value; 191 do { 192 193 // Read key 194 reader.skipWhitespace(); 195 nstring key = reader.readStringJson(); 196 reader.skipWhitespace(); 197 198 // Ensure it's a k-v pair. 199 c = cast(char)reader.readU8(); 200 if (c != ':') 201 return "Invalid key-value pair!"; 202 203 // Read value 204 reader.skipWhitespace(); 205 if (auto error = reader.readJson(value)) { 206 return error; 207 } 208 reader.skipWhitespace(); 209 210 // Assign key-value pair. 211 node[key[]] = value.move(); 212 213 // Consume comma if needed. 214 c = cast(char)reader.readU8(); 215 if (c == ',') { 216 reader.skipWhitespace(); 217 continue; 218 } 219 220 // Comsume object end. 221 if (c == '}') 222 break; 223 224 } while(!reader.eof()); 225 return null; 226 }