Skip to content

Instantly share code, notes, and snippets.

@kranfix
Created June 28, 2024 02:44
Show Gist options
  • Save kranfix/06a86d21eba571b0f65942db55ab83e5 to your computer and use it in GitHub Desktop.
Save kranfix/06a86d21eba571b0f65942db55ab83e5 to your computer and use it in GitHub Desktop.
Draft: Proposal for efficient parsing of json
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