parse_json 2.0.0
parse_json: ^2.0.0 copied to clipboard
Type safe and simple JSON deserialization for Dart with no code generation or reflection.
parse_json #
Type-safe JSON deserialization #
NO CODE GENERATION OR REFLECTION #
parse_json uses named parameters from your constructor to match JSON keys. It supports many features such as optionals, nested types, polymorphism/inheritance, and containers. It is also type-safe, meaning that if you try to deserialize a JSON object into a type that doesn't match, it will throw an error.
Getting Started #
Add the package to your pubspec.yaml:
For Flutter projects:
flutter pub add parse_json
For Dart projects:
dart pub add parse_json
Then import the package in your Dart code:
import 'package:parse_json/parse_json.dart';
Then match up a constructor's named parameters with your JSON data, and make a factory function that uses parse to create an object from JSON. Here is an example of a simple class with primitive members:
Data:
{
"myString": "exampleStr",
"myDouble": 12.5
}
Dart Code:
import 'package:parse_json/parse_json.dart';
final class ExampleObject {
final String myString;
final double myDouble;
/// This is the constructor that will be called by `parse`
const ExampleObject({
required this.myString,
required this.myDouble,
});
/// This is the factory function that will be used to parse the JSON
factory ExampleObject.fromJson(Map<String, dynamic> json) =>
parse(
ExampleObject.new, // the constructor to use
json,
// note: the keys in this map MUST match the named parameters of the constructor
{
'myString': primitive, // strings are primitive, so you just put primitive here
'myDouble': primitive, // doubles are primitive, so you just put primitive here
},
);
}
Note that you do not need to name your member variables the same as the JSON keys. You can specify a special constructor with named parameters that match the JSON keys, and use whatever names you like for your member variables. You also do not need to parse every member variable from JSON, in cases where you have derived data. Your members can also be non-final, but most of the examples in this documentation use final members for consistency.
Data:
{
"myString": "exampleStr",
"myDouble": 12.5
}
Dart Code:
import 'package:parse_json/parse_json.dart';
final class ExampleObject {
/// Members with names different from those in the constructor
final String name;
final double originalHeight;
/// Non-final member
double currentHeight;
/// Constructor that will be called by `parse`
const ExampleObject.json({
required String myString,
required double myDouble,
}) : name = myString,
originalHeight = myDouble,
currentHeight = myDouble;
/// Constructor we use for other stuff
const ExampleObject(this.name, this.originalHeight, this.currentHeight);
/// Factory function for parsing and constructing from JSON
factory ExampleObject.fromJson(Map<String, dynamic> json) =>
parse(
ExampleObject.new,
json,
{
'myString': primitive,
'myDouble': primitive,
},
);
}
At this point you have used primitive for all of your keys. You will need to add more details to the map parameter in parse when you have a JSON property that is non-primitive (Ones that are not String, int, double, bool, or some List or Map of those). Here is an example of a class with non-primitive members:
Data:
{
"myBoolList": [false, true, false],
"myIntList": [12.5, 10.0, 5.0],
"myFriend": {
"myString": "friendStr",
"myDouble": 10.5
},
"myOptionalFriend": ... another friend or this field could be omitted
"friendList": [
... more friends
]
}
Dart Code:
import 'package:parse_json/parse_json.dart';
final class ExampleObject {
final String myString;
final double myDouble;
const ExampleObject({
required this.myString,
required this.myDouble,
});
factory ExampleObject.fromJson(Map<String, dynamic> json) =>
parse(
ExampleObject.new,
json,
{
'myString': primitive,
'myDouble': primitive,
},
);
}
final class ComplexExampleObject {
final List<bool> myBoolList;
final List<int> myIntList;
final ExampleObject myFriend;
final ExampleObject? myOptionalFriend;
final List<ExampleObject> friendList;
const ComplexExampleObject({
required this.myString,
required this.myDouble,
required this.myFriend,
required this.friendList,
this.myOptionalFriend,
});
factory ComplexExampleObject.fromJson(Map<String, dynamic> json) =>
parse(
ComplexExampleObject.new,
json,
{
'myBoolList': primitive,
'myIntList': primitive,
'myFriend': ExampleObject.fromJson.required,
'myOptionalFriend': ExampleObject.fromJson.optional,
'friendList': ExampleObject.fromJson.list,
},
);
}
Example: #
Primitive types: #
If your defining a primitive property (such as String, int, double, bool, or some List or Map of one of those), you can use the primitive constant for your property. Here is an example of a simple class that only has primitive members:
import 'package:parse_json/parse_json.dart';
final class ExampleObject {
final String myString;
final double myDouble;
final int myInt;
final bool myBool;
final String? myOptionalString;
final int? myOptionalInt;
const ExampleObject({
required this.myString,
required this.myDouble,
required this.myInt,
required this.myBool,
this.myOptionalString,
this.myOptionalInt,
});
factory ExampleObject.fromJson(Map<String, dynamic> json) =>
parse(
ExampleObject.new,
json,
{
'myString': primitive,
'myDouble': primitive,
'myInt': primitive,
'myBool': primitive,
'myOptionalString': primitive,
'myOptionalInt': primitive,
}
);
}
User-defined types: #
For non-primitive types (user-defined types), you must use the .required, .optional, .list, .map, .stringMap, or .intMap methods on the user-defined type's fromJson factory function.
import 'package:parse_json/parse_json.dart';
final class SimpleObject {
final String myString;
final double myDouble;
const SimpleObject({
required this.myString,
required this.myDouble,
});
factory SimpleObject.fromJson(Map<String, dynamic> json) =>
parse(
SimpleObject.new,
json,
{
'myString': primitive,
'myDouble': primitive,
}
);
}
final class ComplexObject {
final List<SimpleObject> exampleList;
final Map<String, SimpleObject> exampleMap;
final SimpleObject? optionalExampleObject;
final SimpleObject exampleObject;
const ComplexObject({
required this.exampleList,
required this.exampleMap,
required this.exampleObject,
this.optionalExampleObject,
});
factory ComplexObject.fromJson(Map<String, dynamic> json) =>
parse(
ComplexObject.new,
json,
{
'exampleList': SimpleObject.fromJson.list,
'exampleMap': SimpleObject.fromJson.stringMap,
'exampleObject': SimpleObject.fromJson.required,
'optionalExampleObject': SimpleObject.fromJson.optional,
}
);
}
Inheritance/Polymorphic types: #
With polymorphic base types, you need to use polymorphicParse. You will need to provide a polymorphicKey and a polymorphicId for each subclass. The polymorphicKey is the key in the JSON that will be used to determine the type of the object. The polymorphicId is the value of the polymorphicKey that will be used to determine the type of the object. You must provide the fromJson factory functions to the derivedTypes parameter of polymorphicParse, and use the polymorphicId of each subclass as the key in the map. You can also provide a baseDefinition to the polymorphicParse function that will be used to parse the base class if it is not abstract and polymorphicKey is missing from the JSON.
import 'package:parse_json/parse_json.dart';
final class BaseClass {
static const polymorphicKey = 'type';
final String myString;
final double myDouble;
const BaseClass({
required this.myString,
required this.myDouble,
});
factory BaseClass.fromJson(Map<String, dynamic> json) =>
polymorphicParse(
polymorphicKey,
json,
{
SubClassA.polymorphicId: SubClassA.fromJson,
SubClassB.polymorphicId: SubClassB.fromJson,
},
baseDefinition: DefinedType(BaseClass.new, {
'myString': primitive,
'myDouble': primitive,
}));
}
final class SubClassA extends BaseClass {
static const polymorphicId = 'A';
final int myInt;
const SubClassA({
required super.myString,
required super.myDouble,
required this.myInt,
}) : super();
factory SubClassA.fromJson(Map<String, dynamic> json) =>
parse(
SubClassA.new,
json,
{
'myString': primitive,
'myDouble': primitive,
'myInt': primitive,
},
);
}
final class SubClassB extends BaseClass {
static const polymorphicId = 'B';
final ExampleObject myExampleObject;
const SubClassB({
required super.myString,
required super.myDouble,
required this.myExampleObject,
}) : super();
factory SubClassB.fromJson(Map<String, dynamic> json) =>
parse(
SubClassB.fromJson,
json,
{
'myString': primitive,
'myDouble': primitive,
'myExampleObject': ExampleObject.fromJson.required,
},
);
}
License/Disclaimer #
See LICENSE