Created
June 28, 2024 02:44
-
-
Save kranfix/06a86d21eba571b0f65942db55ab83e5 to your computer and use it in GitHub Desktop.
Draft: Proposal for efficient parsing of json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Store { | |
Store(this.name, this.address, this.geoPosition, this.employees); | |
Store.fromUnsafeMap(Map<String, dynamic> json) | |
: name = json['name'], | |
address = json['address'], | |
geoPosition = json['geoPosition'], | |
employees = json['employees']; | |
final String name; | |
final String? address; | |
final GeoPosition geoPosition; | |
final List<Employee> employees; | |
} | |
class Employee { | |
Employee(this.name, this.email); | |
Employee.fromUnsafeMap(Map<String, dynamic> json) | |
: name = json['name'], | |
email = json['email']; | |
final String name; | |
final String email; | |
} | |
class GeoPosition { | |
GeoPosition(this.lat, this.lon); | |
GeoPosition.fromUnsafeMap(Map<String, dynamic> json) | |
: lat = json['lat'], | |
lon = json['lon']; | |
final double lat; | |
final double lon; | |
} | |
const storeJson = '{"name": "Taco-judo", "address": "Judo Street", "geoPosition": {"lat": 37.7749, "lon": -122.4194} }'; | |
const storeJsonBad = '{"name": 4, "address": "Judo Street", "geoPosition": {"lat": 37.7749, "lon": -122.4194} }'; | |
(Store, Context) storeParser(Context ctx, {bool stopOnCompleted = false}) { | |
final parserMap = <String, Parser<dynamic>>{ | |
"name": stringParser, | |
"address": nullable(stringParser), | |
"geoPosition": geoPositionParser, | |
"employees": array(employeeParser), | |
}; | |
final (storeMap, currentCtx) = keyValue<dynamic>(parserMap, stopOnCompleted: stopOnCompleted)(ctx); | |
return (Store.fromUnsafeMap(storeMap), currentCtx); | |
} | |
// (Store, Context) storeEfficientParser(Context ctx, {bool stopOnCompleted = false}) { | |
// var currentCtx = ctx; | |
// final (name, nameCtx) = fieldParser("email", stringParser)(currentCtx); | |
// currentCtx = nameCtx; | |
// final (email, emailCtx) = maybeFieldParser("email", stringParser)(currentCtx); | |
// currentCtx = emailCtx; | |
// final (geoPosition, geoPositionCtx) = fieldParser("geoPosition", geoPositionParser)(currentCtx); | |
// currentCtx = geoPositionCtx; | |
// final (employees, employeesCtx) = fieldParser("employees", array(employeeParser))(currentCtx); | |
// currentCtx = employeesCtx; | |
// } | |
(GeoPosition, Context) geoPositionParser(Context ctx) { | |
final parserMap = <String, Parser<dynamic>>{ | |
"lat": doubleParser, | |
"lon": doubleParser, | |
}; | |
final (geoMap, currentCtx) = keyValue<dynamic>(parserMap)(ctx); | |
return (GeoPosition.fromUnsafeMap(geoMap), currentCtx); | |
} | |
(Employee, Context) employeeParser(Context ctx) { | |
final parserMap = <String, Parser<dynamic>>{ | |
"name": stringParser, | |
"email": stringParser, | |
}; | |
final (employeeMap, currentCtx) = keyValue<dynamic>(parserMap)(ctx); | |
return (Employee.fromUnsafeMap(employeeMap), currentCtx); | |
} | |
typedef Context = String; | |
Context ignoreWhiteSpaces(Context json) { | |
throw 'unimplemented'; | |
} | |
Context ignoreLiteral(Context ctx, String literal, {bool optional = true}) { | |
try { | |
final (_, newCtx) = literalParser(ctx, literal); | |
return newCtx; | |
} catch (_) { | |
if (optional) { | |
return ctx; | |
} else { | |
rethrow; | |
} | |
} | |
} | |
(String, Context) literalParser(Context ctx, String literal) { | |
throw 'unimplemented'; | |
return (literal, ctx); | |
} | |
(int, Context) intParser(String json) { | |
throw 'unimplemented'; | |
} | |
(double, Context) doubleParser(String json) { | |
throw 'unimplemented'; | |
} | |
(Null, Context) nullParser(String json) { | |
throw 'unimplemented'; | |
} | |
(num, Context) numParser(String json) { | |
throw 'unimplemented'; | |
} | |
(String, Context) stringParser(String json) { | |
throw 'unimplemented'; | |
} | |
typedef Parser<T> = (T, Context) Function(Context ctx); | |
Parser<T?> nullable<T extends Object>(Parser<T> parser) { | |
return (Context ctx) { | |
try { | |
return nullParser(ctx); | |
} catch(_) { | |
return parser(ctx); | |
} | |
}; | |
} | |
Parser<List<T>> array<T extends Object>(Parser<T> parser) { | |
return (Context ctx) { | |
var currentCtx = ctx; | |
currentCtx = ignoreLiteral(ctx, "["); | |
final list = <T>[]; | |
var shouldTryParseOneElement = true; | |
while(true) { | |
try { | |
currentCtx = ignoreWhiteSpaces(currentCtx); | |
currentCtx = ignoreLiteral(ctx, "]"); | |
return (list, currentCtx); | |
} catch (_) { | |
if(!shouldTryParseOneElement) { | |
throw Exception('Unexpected'); | |
} | |
final (value, newCtx) = parser(currentCtx); | |
list.add(value); | |
shouldTryParseOneElement = false; | |
currentCtx = newCtx; | |
currentCtx = ignoreLiteral(currentCtx, ",", optional: true); | |
shouldTryParseOneElement = currentCtx != newCtx; | |
} | |
} | |
}; | |
} | |
Parser<Map<String, V>> keyValue<V extends dynamic>(Map<String, Parser<V>> parserMap, {bool stopOnCompleted = false}) { | |
return (Context ctx) { | |
var currentCtx = ctx; | |
currentCtx = ignoreLiteral(ctx, "{"); | |
final dataMap = <String, V>{}; | |
var shouldTryParseNextElement = true; | |
while (true) { | |
currentCtx = ignoreWhiteSpaces(ctx); | |
try { | |
currentCtx = ignoreLiteral(ctx, "}"); | |
if(parserMap.isEmpty) { | |
return (dataMap, currentCtx); | |
} else { | |
// TODO: apply nullable and default values if the fields don't appear | |
shouldTryParseNextElement = false; | |
throw Exception('Missing data'); | |
} | |
} catch(_) { | |
if(!shouldTryParseNextElement) { | |
rethrow; | |
} | |
if(parserMap.isEmpty && stopOnCompleted) { | |
return (dataMap, currentCtx); | |
} | |
final (key, newCtx) = stringParser(currentCtx); | |
currentCtx = ignoreWhiteSpaces(ctx); | |
currentCtx = ignoreLiteral(currentCtx, ":"); | |
currentCtx = ignoreWhiteSpaces(ctx); | |
final (value, newCtx1) = parserMap[key]!(newCtx); | |
dataMap[key] = value; | |
parserMap.remove(key); | |
currentCtx = newCtx1; | |
currentCtx = ignoreWhiteSpaces(ctx); | |
shouldTryParseNextElement = false; | |
currentCtx = newCtx; | |
currentCtx = ignoreLiteral(currentCtx, ",", optional: true); | |
shouldTryParseNextElement = currentCtx != newCtx; | |
} | |
} | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment