Consider this code
class Album {
int userId;
int id;
String title;
Album({this.userId, this.id, this.title});
Album.fromJsonN(Map<String, dynamic> json) {
this.userId = json['userId'];
this.id = json['id'];
this.title = json['title'];
}
factory Album.fromJson(Map<String, dynamic> json) {
return Album(userId: json['userId'], id: json['id'], title: json['title']);
}
}
In most tutorials the explanation for why we use factory for json mapping method is: "we use the factory keyword when implementing a constructor that doesn’t always create a new instance of its class".
in factory method in above code, doesn't it returning a new instance? if it does, so whats the reason for using factory here?
and whats the difference between factory constructor and fromJsonN named constructor in this context?
A Dart class may have generative constructors or factory
constructors. A generative constructor is a function that always
returns a new instance of the class. Because of this, it does not
utilize the return keyword.
A factory constructor has looser constraints than a generative constructor. The factory need only return an instance that is the same
type as the class or that implements its methods (ie satisfies its
interface). This could be a new instance of the class, but could also
be an existing instance of the class or a new/existing instance of a
subclass (which will necessarily have the same methods as the parent).
A factory can use control flow to determine what object to return, and
must utilize the return keyword. In order for a factory to return a
new class instance, it must first call a generative constructor.
Please see also Understanding Factory constructor code example - Dart for a very detailed explanation.
So for your question: Yes it is returning a new Instance but i guess the speciality comes from the fact that you the factory constructor is capable of creating an object based on an incoming json map whereas the generative constructor is used to instantiate a new object from single attributes.
And for your last question: Both do the same, namely returning an instance of the class given a json map. The techincal difference is that one is a generative and one a factory constructor.
One of the use cases when to use factory constructor or named constructor.
To initialise the final fields of the class you have to do it in initializer list or in the declaration when using named constructor. On the other hand using factory constructor You can initialise the final fields in the body of the constructor.
Related
I am new to this serialization, I am facing a hard time understanding the difference between these two functionalities while serialization in dart/flutter #JsonKey(name: invoice) && #JsonKey(toJson: toJSON_SalesInvoiceDetail)
I see my friend wrote this below function outside the class for the above json key #JsonKey(toJson: toJSON_SalesInvoiceDetail) as I see this maps another class so it's kind of embedding one class into another but I do not know how this serialization is happening under the hood, could anyone guide me about this pls, Your help will be appreciated a lot, thanks in advance
The param toJson changes the value of JSON(only the method toJson), and the param name changes the key (both of the methods toJson and fromJson).
For example, I made a class like the following.
#JsonSerializable()
class Any {
const Any(this.param);
factory Any.fromJson(Map<String, dynamic> json) => _$AnyFromJson(json);
#JsonKey(name: 'json_key_of_param', toJson: paramToJson)
final int param;
Map<String, dynamic> toJson() => _$AnyToJson(this);
}
String paramToJson(int paramValue) {
return 'param value is $paramValue';
}
And if we use this like
print(const Any(100).toJson());
then printed the following.
{json_key_of_param: param value is 100}
In this case, I didn't specify the param fromJson, so we should use the method fromJson like the following.
Any.fromJson(<String, dynamic>{'json_key_of_param': 100});
I am upgrading my flutter project to null safety support. Inside this project I have abstract model classes and inheritances. They have looked like so:
abstract class Technology {
Guid id;
String name;
String description;
String assetName;
Technology();
factory Technology.fromJson(Map<String, dynamic> json) =>
TechnologyConverter().fromJson(json);
Map<String, dynamic> toJson() => TechnologyConverter().toJson(this);
}
abstract class Research extends Technology {
ResearchType researchType;
Research();
factory Research.fromJson(Map<String, dynamic> json) =>
ResearchConverter().fromJson(json);
}
class LevelableResearch extends Research {
LevelableResearch();
factory LevelableResearch.fromJson(Map<String, dynamic> json) =>
_$LevelableResearchFromJson(json);
Map<String, dynamic> toJson() => _$LevelableResearchToJson(this);
}
This was no problem for the build_runner to generate the json serializer.
Now as I updated to null safety, I get a lot of errors, starting with "Non-nullable instance field 'id' must be initialized. Try adding an initializer expression, or add a field initializer in this constructor, or mark it 'late'"
If I initialize the field within the constructor, I have to call ": super()" with all the needed fields from the inherited class, which results in way too much writing.
As the error says, I could mark it with late, but I am not sure if this is the way it should be done.
What is the correct way to write these classes, so the build_runner can generate a correct json serializer?
If I initialize the field within the constructor, I have to call ": super()" with all the needed fields from the inherited class, which results in way too much writing.
That's true only if you modified your constructor to take required arguments. Required arguments naturally prevent derived class's constructors from implicitly calling super() to construct the base class.
In general (i.e., not specifically for the JSON deserialization library you're using), if you don't want to have required arguments for your base class constructor, then you can:
Initialize members to non-null default values, either via your constructor's initializer list or by using inline field initializers.
Mark the members late. Do this only if you can guarantee that the members will be initialized before they are accessed.
Make the members nullable. If you then add null assertions (!) wherever the members are unconditionally accessed, this is equivalent to what your code did before null-safety. (Or add null-checks that fail gracefully.)
I am trying to convert the object "Week" to json.
https://flutter.dev/docs/development/data-and-backend/json this is the source that i used
class Week{
DateTime _startDate;
DateTime _endDate;
List<Goal> _goalList;
String _improvement;
Week(this._startDate, this._endDate){
this._goalList = List<Goal>();
this._improvement = "";
}
Week.fromJson(Map<String, dynamic> json)
: _startDate = json['startDate'],
_endDate = json['endDate'],
_goalList = json['goalList'],
_improvement = json['improvement'];
Map<String, dynamic> toJson() =>
{
'startDate': _startDate,
'endDate': _endDate,
'goalList': _goalList,
'improvement': _improvement,
};
}
I used this:
DateTime startDate = currentDate.subtract(new Duration(days:(weekday-1)));
DateTime endDate = currentDate.add(new Duration(days:(7-weekday)));
Week week = new Week(startDate, endDate);
var json = jsonEncode(week);
But the problem is that I get this result:
Unhandled Exception: Converting object to an encodable object failed: Instance of 'Week'
#0 _JsonStringifier.writeObject (dart:convert/json.dart:647:7)
#1 _JsonStringStringifier.printOn (dart:convert/json.dart:834:17)
#2 _JsonStringStringifier.stringify (dart:convert/json.dart:819:5)
#3 JsonEncoder.convert (dart:convert/json.dart:255:30)
#4 JsonCodec.encode (dart:convert/json.dart:166:45)
#5 jsonEncode (dart:convert/json.dart:80:10)
jsonEncode requires a Map<String, dynamic>, not a Week object. Calling your toJson() method should do the trick.
var json = jsonEncode(week.toJson());
However, keep in mind that your toJson() method is also incorrect, as things like _goalList and the dates are still objects, not Maps or Lists. You'll need to implement toJson methods on those as well.
To answer your specific questions:
Because dart is not javascript / typescript. Dart checks types at runtime, therefore you have to explicitly tell it how to convert things - also there is no reflection in dart, so it can't figure it out by itself.
You can use a library that uses code generation to do these things automatically for you - it still wont be possible at runtime though - read more about JSON serialization.
The easiest way would be to implement the methods directly in the classes, as that's where you have access to in your root object. Keep in mind that the structure that jsonEncode needs is a Map<String, dynamic>, but the dynamic part really means List<dynamic>, Map<String, dynamic> or a primitive that is json compatible such as String or double - if you try to imagine how such a nested structure of said types looks, you'll realise that it's basically json. So when you do something like 'goalList': _goalList, you're giving it an object, which is not one of the allowed types.
Hope this clears things up a bit.
for anyone wondering: I got my solution.
To make my code work I needed to implement the toJson() methods at my class Goal as well (because I used List<Goal> in Week).
class Goal{
String _text;
bool _reached;
Map<String, dynamic> toJson() =>
{
'text': _text,
'reached': _reached,
};
}
Also, I needed to add .toIso8601String() to the DateTime objects like that in the Week class:
Map<String, dynamic> toJson() =>
{
'startDate': _startDate.toIso8601String(),
'endDate': _endDate.toIso8601String(),
'goalList': _goalList,
'improvement': _improvement,
};
Now the output is:
{"startDate":"2019-05-27T00:00:00.000Z","endDate":"2019-06-02T00:00:00.000Z","goalList":[],"improvement":""}
Taking suggestion 2 from #Phillip's answer above for Json serialization, the Freezed package I believe you can skip the #JsonSerializable annotation and just used the #Freezed annotation because Freezed "will automatically ask json_serializable to generate all the necessary fromJson/toJson."
So the example here: https://flutter.dev/docs/development/data-and-backend/json#use-code-generation-for-medium-to-large-projects
becomes:
//import 'package:json_annotation/json_annotation.dart';
import 'freezed_annotation/freezed_annotation.dart';
/// This allows the `User` class to access private members in
/// the generated file. The value for this is *.g.dart, where
/// the star denotes the source file name.
part 'user.g.dart';
part 'user.freezed.dart';
/// An annotation for the code generator to know that this class needs the
/// JSON serialization logic to be generated.
#freezed
class User {
User(this.name, this.email);
String name;
String email;
/// A necessary factory constructor for creating a new User instance
/// from a map. Pass the map to the generated `_$UserFromJson()` constructor.
/// The constructor is named after the source class, in this case, User.
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
/// `toJson` is the convention for a class to declare support for serialization
/// to JSON. The implementation simply calls the private, generated
/// helper method `_$UserToJson`.
Map<String, dynamic> toJson() => _$UserToJson(this);
}
Freezed: https://pub.dev/packages/freezed
Don't forget to edit pubspec.yaml for freezed and freezed_annotation
I have some niggling questions about factory constructors example mentioned here (https://www.dartlang.org/guides/language/language-tour#factory-constructors).
I am aware of only three types of constructors on a basic level - default, named and parameterised.
Why should I use factory at all for this example?
Is that a named constructor which is being used? and why?
tl;dr Use a factory in situations where you don't necessarily want to return a new instance of the class itself. Use cases:
the constructor is expensive, so you want to return an existing instance - if possible - instead of creating a new one;
you only ever want to create one instance of a class (the singleton pattern);
you want to return a subclass instance instead of the class itself.
Explanation
A Dart class may have generative constructors or factory constructors. A generative constructor is a function that always returns a new instance of the class. Because of this, it does not utilize the return keyword. A common generative constructor is of the form:
class Person {
String name;
String country;
// unnamed generative constructor
Person(this.name, this.country);
}
var p = Person("...") // returns a new instance of the Person class
A factory constructor has looser constraints than a generative constructor. The factory need only return an instance that is the same type as the class or that implements its methods (ie satisfies its interface). This could be a new instance of the class, but could also be an existing instance of the class or a new/existing instance of a subclass (which will necessarily have the same methods as the parent). A factory can use control flow to determine what object to return, and must utilize the return keyword. In order for a factory to return a new class instance, it must first call a generative constructor.
In your example, the unnamed factory constructor first reads from a Map property called _cache (which, because it is Static, is stored at the class level and therefore independent of any instance variable). If an instance variable already exists, it is returned. Otherwise, a new instance is generated by calling the named generative constructor Logger._internal. This value is cached and then returned. Because the generative constructor takes only a name parameter, the mute property will always be initialized to false, but can be changed with the default setter:
var log = Logger("...");
log.mute = true;
log.log(...); // will not print
The term factory alludes to the Factory Pattern, which is all about allowing a constructor to return a subclass instance (instead of a class instance) based on the arguments supplied. A good example of this use case in Dart is the abstract HTML Element class, which defines dozens of named factory constructor functions returning different subclasses. For example, Element.div() and Element.li() return <div> and <li> elements, respectively.
In this caching application, I find "factory" a bit of a misnomer since its purpose is to avoid calls to the generative constructor, and I think of real-world factories as inherently generative. Perhaps a more suitable term here would be "warehouse": if an item is already available, pull it off the shelf and deliver it. If not, call for a new one.
How does all this relate to named constructors? Generative and factory constructors can both be either unnamed or named:
...
// named generative
// delegates to the default generative constructor
Person.greek(String name) : this(name, "Greece");
// named factory
factory Person.greek(String name) {
return Greek(name);
}
}
class Greek extends Person {
Greek(String name) : super(name, "Greece");
}
There is not much difference between a static method and a factory constructor.
For a factory constructor the return type is fixed to the type of the class while for a static method you can provide your own return type.
A factory constructor can be invoked with new, but that became mostly irrelevant with optional new in Dart 2.
There are other features like redirects rather rarely used that are supported for (factory) constructors but not for static methods.
It is probably still good practice to use a factory constructor to create instances of classes instead of static methods to make the purpose of object creation more obvious.
This is the reason a factory constructor is used in the example you posted and perhaps because the code was originally written in Dart 1 where it allowed to create a logger instance with new like with any other class.
Yes this is a named constructor and the prefix _ makes it a private named constructor. Only named constructors can be made private because otherwise there would be no place to add the _ prefix.
It is used to prevent instance creation from anywhere else than from the public factory constructor. This way it is ensured there can't be more than one Logger instance in your application.
The factory constructor only creates an instance the first time, and for subsequent calls always returns the previously created instance.
Complementing Dave's answer, this code shows a clear example when use factory to return a parent related class.
Take a look a this code from https://codelabs.developers.google.com/codelabs/from-java-to-dart/#3
You can run this code here. https://dartpad.dartlang.org/63e040a3fd682e191af40f6427eaf9ef
Make some changes in order to learn how it would work in certain situations, like singletons.
import 'dart:math';
abstract class Shape {
factory Shape(String type) {
if (type == 'circle') return Circle(2);
if (type == 'square') return Square(2);
// To trigger exception, don't implement a check for 'triangle'.
throw 'Can\'t create $type.';
}
num get area;
}
class Circle implements Shape {
final num radius;
Circle(this.radius);
num get area => pi * pow(radius, 2);
}
class Square implements Shape {
final num side;
Square(this.side);
num get area => pow(side, 2);
}
class Triangle implements Shape {
final num side;
Triangle(this.side);
num get area => pow(side, 2) / 2;
}
main() {
try {
print(Shape('circle').area);
print(Shape('square').area);
print(Shape('triangle').area);
} catch (err) {
print(err);
}
}
In addition to the other answers, also consider the order of instantiating objects and when the instance is created:
In normal constructor, an instance gets created and the final variables get instantiated with the initializer list. This is why there's no return statement. The instance to return is already fixed, when executing the constructor!
In a factory constructor, the instance to return is decided by the method. That's why it needs a return statement and why it'll usually call a normal constructor in at least one path.
So a factory does nothing different than a static method could do (in other languages often called getInstance()), except you cannot overload the constructor with a static method but you can with a factory method. I.e. factory methods are a way to hide the fact that the user of your class is not calling a constructor but a static method:
// C++
MyCoolService.getInstance()
// Dart
MyCoolService()
I have a class that has more than a dozen properties. For most of the properties of primitive type, I hope to use the default BeanSerializer and BeanDeserializer or whatever to reduce the cumbersome code I need to write. For other properties of custom and array types, I want to do some custom serializer/deserializer. Note that I am not able to change the underlying JSON string. But I have full access to the android code. I am using Jackson 1.7.9/Ektorp 1.1.1.
shall I subclass BeanDeserializer? I am having trouble with that. It expects a default constructor with no parameters but I don't know how to call the super constructor.
class MyType{
// a dozen properties with primitive types String, Int, BigDecimal
public Stirng getName();
public void setName(String name);
// properties that require custom deserializer/serializer
public CustomType getCustom();
public void setCustom(CustomType ct);
}
class MyDeserializer extends BeanDeserialzer{
// an exception is throw if I don't have default constructor.
// But BeanDeserializer doesn't have a default constructor
// It has the below constructor that I don't know how to fill in the parameters
public MyDeserializer(AnnotatedClass forClass, JavaType type,
BeanProperty property, CreatorContainer creators,
BeanPropertyMap properties,
Map<String, SettableBeanProperty> backRefs,
HashSet<String> ignorableProps, boolean ignoreAllUnknown,
SettableAnyProperty anySetter) {
super(forClass, type, property, creators, properties, backRefs, ignorableProps,
ignoreAllUnknown, anySetter);
}
#Override
public Object deserialize(JsonParser jp, DeserializationContext dc, Object bean)
throws IOException, JsonProcessingException {
super.deserialize(jp, dc, bean);
MyType c = (MyType)bean;
ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.readValue(jp, JsonNode.class);
// Use tree model to construct custom
// Is it inefficient because it needs a second pass to the JSON string to construct the tree?
c.setCustom(custom);
return c;
}
}
I searched Google but couldn't find any helpful examples/tutorial. If anyone can send me some working examples that would be great! Thanks!
To sub-class BeanSerializer/-Deserializer, you would be better off using a more recent version of Jackson, since this area has been improved with explicit support via BeanSerializerModifier and BeanDeserializerModifier, which can alter configuration of instances.
But just to make sure, you can also specify custom serializer/deserializer to just be used on individual properties, like so:
class Foo {
#JsonSerialize(using=MySerializer.class)
public OddType getValue();
}