Convert JS object into Dart classes - dart

What is the best pattern to use to convert objects from Javascript to their Dart class counter parts?
// car.dart
import 'part.dart';
class Car {
String paintColor;
List<Part> parts;
}
// part.dart
class Part {
String name;
String SKU;
}
// main.dart
import 'dart:html';
import 'dart:js';
import 'car.dart';
void main() {
var body = document.querySelector('body');
body.addEventListener('carSelect', loadCarHandler, false);
}
void loadCarHandler(event) {
// this is the contents of a CustomEvent from outside dart
// goal is to convert it into Car and Parts
LinkedHashMap obj = event.detail;
/*
this is what the `obj` looks like inside the debugger
obj = _LinkedHashMap
:paintColor = 'Red'
:parts = List[2]
0 = _LinkedHashMap
:name = 'Wheel'
:SKU = 'Z123
1 = _LinkedHashMap
:name = 'Tire'
:SKU = 'Z456'
*/
}
Should I do a conversion in the handler?
Allow the constructor to take a LinkedHashMap and convert it there?
Create a factory?
Is there something built into Dart I'm not aware of that would handle this?
What is the preferred dart way of handling this?

There are several libraries that allow to create Dart object from JSON datas. See morph, dartson or serialization.
You can also avoid mirrors by adding a constructor like this :
class Car {
String paintColor;
List<Part> parts;
Car();
Car.fromJson(json)
: paintColor = json['paintColor'],
parts = json['parts'].map((e) => new Part.fromJson(e)).toList();
}
class Part {
String name;
String SKU;
Part();
Part.fromJson(json)
: name = json['name'],
SKU = json['SKU'];
}

Related

How to generate Dart code for annotations at fields?

I'm writing a code generator for Dart using the build_runner, but my builder is not being called for annotations at fields, although it does work for annotations at classes.
Is it possible to also call the generator for annotations at fields (or at any place for that matter)?
For example, the builder is called for the following file:
import 'package:my_annotation/my_annotation.dart';
part 'example.g.dart';
#MyAnnotation()
class Fruit {
int number;
}
But not for this one:
import 'package:my_annotation/my_annotation.dart';
part 'example.g.dart';
class Fruit {
#MyAnnotation()
int number;
}
Here's the definition of the annotation:
class MyAnnotation {
const MyAnnotation();
}
And this is how the generator is defined. For now, it just aborts whenever it's called, causing an error message to be printed.
library my_annotation_generator;
import 'package:analyzer/dart/element/element.dart';
import 'package:build/build.dart';
import 'package:my_annotation/my_annotation.dart';
import 'package:source_gen/source_gen.dart';
Builder generateAnnotation(BuilderOptions options) =>
SharedPartBuilder([MyAnnotationGenerator()], 'my_annotation');
class MyAnnotationGenerator extends GeneratorForAnnotation<MyAnnotation> {
#override
generateForAnnotatedElement(Element element, ConstantReader annotation, _) {
throw CodeGenError('Generating code for annotation is not implemented yet.');
}
Here's the build.yaml configuration:
targets:
$default:
builders:
my_annotation_generator|my_annotation:
enabled: true
builders:
my_annotation:
target: ":my_annotation_generator"
import: "package:my_annotation/my_annotation.dart"
builder_factories: ["generateAnnotation"]
build_extensions: { ".dart": [".my_annotation.g.part"] }
auto_apply: dependents
build_to: cache
applies_builders: ["source_gen|combining_builder"]
At least from my experience, your file 'example.dart' would need at least one annotation above the class definition to be parsed by GeneratorForAnnotation.
example.dart:
import 'package:my_annotation/my_annotation.dart';
part 'example.g.dart';
#MyAnnotation()
class Fruit {
#MyFieldAnnotation()
int number;
}
To access annotations above class fields or class methods you could use a visitor to "visit" each child element and extract the source code information.
For example, to get information about the class fields you could override the method visitFieldElement and then access any annotations using the getter: element.metadata.
builder.dart:
import 'dart:async';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/visitor.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:build/src/builder/build_step.dart';
import 'package:source_gen/source_gen.dart';
import 'package:my_annotation/my_annotation.dart';
class MyAnnotationGenerator extends
GeneratorForAnnotation<MyAnnotation> {
#override
FutureOr<String> generateForAnnotatedElement(
Element element,
ConstantReader annotation,
BuildStep buildStep,){
return _generateSource(element);
}
String _generateSource(Element element) {
var visitor = ModelVisitor();
element.visitChildren(visitor);
return '''
// ${visitor.className}
// ${visitor.fields}
// ${visitor.metaData}
''';
}
}
class ModelVisitor extends SimpleElementVisitor {
DartType className;
Map<String, DartType> fields = {};
Map<String, dynamic> metaData = {};
#override
visitConstructorElement(ConstructorElement element) {
className = element.type.returnType;
}
#override
visitFieldElement(FieldElement element) {
fields[element.name] = element.type;
metaData[element.name] = element.metadata;
}
}
Note: In this example, _generateSource returns a commented statement. Without comments you would need to return well-formed dart source code, otherwise, the builder will terminate with an error.
For more information see:
Source Generation and Writing Your Own Package (The Boring Flutter Development Show, Ep. 22) https://www.youtube.com/watch?v=mYDFOdl-aWM&t=459s
The built-in GeneratorForAnnotation uses the LibraryElement's annotatedWith(...) method, which only checks for top-level annotations.
To also detect annotations on fields, you'll need to write something custom.
Here's the Generator I wrote for my project:
abstract class GeneratorForAnnotatedField<AnnotationType> extends Generator {
/// Returns the annotation of type [AnnotationType] of the given [element],
/// or [null] if it doesn't have any.
DartObject getAnnotation(Element element) {
final annotations =
TypeChecker.fromRuntime(AnnotationType).annotationsOf(element);
if (annotations.isEmpty) {
return null;
}
if (annotations.length > 1) {
throw Exception(
"You tried to add multiple #$AnnotationType() annotations to the "
"same element (${element.name}), but that's not possible.");
}
return annotations.single;
}
#override
String generate(LibraryReader library, BuildStep buildStep) {
final values = <String>{};
for (final element in library.allElements) {
if (element is ClassElement && !element.isEnum) {
for (final field in element.fields) {
final annotation = getAnnotation(field);
if (annotation != null) {
values.add(generateForAnnotatedField(
field,
ConstantReader(annotation),
));
}
}
}
}
return values.join('\n\n');
}
String generateForAnnotatedField(
FieldElement field, ConstantReader annotation);
}
I had a very similar issue trying to target specific methods within my annotated classes. Inspired by your answers I slightly modified the class annotation model_visitor to check the method annotation before selecting elements.
class ClassAnnotationModelVisitor extends SimpleElementVisitor<dynamic> {
String className;
Map<String, String> methods = <String, String>{};
Map<String, String> parameters = <String, String>{};
#override
dynamic visitConstructorElement(ConstructorElement element) {
final elementReturnType = element.type.returnType.toString();
className = elementReturnType.replaceFirst('*', '');
}
#override
dynamic visitMethodElement(MethodElement element) {
if (methodHasAnnotation(MethodAnnotation, element)) {
final functionReturnType = element.type.returnType.toString();
methods[element.name] = functionReturnType.replaceFirst('*', '');
parameters[element.name] = element.parameters.map((e) => e.name).join(' ,');
}
}
bool methodHasAnnotation(Type annotationType, MethodElement element) {
final annotations = TypeChecker.fromRuntime(annotationType).annotationsOf(element);
return !annotations.isEmpty;
}
}
Then, I can use the basic GeneratorForAnnotation class and generate for class and methodsArray.

Dart Multiple Constructors

Is it really not possible to create multiple constructors for a class in dart?
in my Player Class, If I have this constructor
Player(String name, int color) {
this._color = color;
this._name = name;
}
Then I try to add this constructor:
Player(Player another) {
this._color = another.getColor();
this._name = another.getName();
}
I get the following error:
The default constructor is already defined.
I'm not looking for a workaround by creating one Constructor with a bunch of non required arguments.
Is there a nice way to solve this?
You can only have one unnamed constructor, but you can have any number of additional named constructors
class Player {
Player(String name, int color) {
this._color = color;
this._name = name;
}
Player.fromPlayer(Player another) {
this._color = another.getColor();
this._name = another.getName();
}
}
new Player.fromPlayer(playerOne);
This constructor
Player(String name, int color) {
this._color = color;
this._name = name;
}
can be simplified to
Player(this._name, this._color);
Named constructors can also be private by starting the name with _
class Player {
Player._(this._name, this._color);
Player._foo();
}
Constructors with final fields initializer list are necessary:
class Player {
final String name;
final String color;
Player(this.name, this.color);
Player.fromPlayer(Player another) :
color = another.color,
name = another.name;
}
If your class uses final parameters the accepted answer will not work. This does:
class Player {
final String name;
final String color;
Player(this.name, this.color);
Player.fromPlayer(Player another) :
color = another.color,
name = another.name;
}
If you already used a constructor with params in the project and now you figured out that you need some no params default constructor you can add an empty constructor.
class User{
String name;
User({this.name}); //This you already had before
User.empty(); //Add this later
}
Try the below code on DartPad
class MyClass {
//These two are private attributes
int _age;
String _name;
//This is a public attribute
String defaultName = "My Default Name!";
//Default constructor
MyClass() {
_age = 0;
_name = "Anonymous";
}
MyClass.copyContructor(MyClass fromMyClass) {
this._age = fromMyClass._age;
this._name = fromMyClass._name;
}
MyClass.overloadedContructor(String name, int age) {
this._age = age;
this._name = name;
}
MyClass.overloadedContructorNamedArguemnts({String name, int age}) {
this._age = age;
this._name = name;
}
//Overriding the toString() method
String toString() {
String retVal = "Name:: " + _name + " | " + "Age:: " + _age.toString();
return retVal;
}
}
//The execution starts from here..
void main() {
MyClass myClass1 = new MyClass();
//Cannot access oprivate attributes
//print(myClass1.name);
//print(myClass1.age);
//Can access the public attribute
print("Default Name:: " + myClass1.defaultName);
print(myClass1.toString());
MyClass myClass2 = new MyClass.copyContructor(myClass1);
print(myClass2.toString());
MyClass myClass3 = new MyClass.overloadedContructor("Amit", 42);
print(myClass3.toString());
MyClass myClass4 =
new MyClass.overloadedContructorNamedArguemnts(age: 42, name: "Amit");
print(myClass4.toString());
}
Dart doesn't support parameter overloading (having multiple functions of the same name but with different parameters). This applies to constructors as well - that's the reason why in SDK there're so many classes with named constructors.
In Dart you can use Default Constructor, Named Constructor, Factory Method and Static Method to instantiate classes:
class A {
// Default constructor
A() : msg = '1';
// Named constructor with positional param
A.message(this.msg);
// Factory method with named param
factory A.underscore({String msg = ''}) {
return A.message('_'+msg);
}
// Factory method with arrow func body
static A bang(msg) => A.message('!'+msg);
final String msg;
}
void main() {
print(A().msg);
print(A.message('2').msg);
print(A.underscore(msg: '3').msg);
print(A.bang('4').msg);
}
Output:
1
2
_3
!4
You can use factory constructors
factory Player.fromPlayer(Player another) => Player(another.name, another.color);
i had found solution to solve this problem depend on checked the type of data you are passed it to function
Try this Solution
As Günter Zöchbauer already specified in his answer:
You can only have one unnamed constructor, but you can have any number of additional named constructors in Flutter.
By using named constructor you can create multiple constructors in the same class.
Each constructor will have a unique name. So that you can identify each of them.
Syntax for named constructor :
class_name.constructor_name (arguments) {
// If there is a block of code, use this syntax
// Statements
}
or
class_name.constructor_name (arguments);
// If there is no block of code, use this syntax
For more insights Click Here
To know about various types of constructors in Flutter Click Here
Class User{
User();
User.fromName(this.name);
String? name;
}
If you want to do some more elaborated property calculation (I'm a Swift guy), you can do like this:
class FooProvider {
int selectedFoo;
FooProvider(List<String> usageObjects)
: selectedFoo = firstOne(usageObjects);
static int firstOne(List<String> usageObjects) {
return 2;
}
}

How to use a custom Coder in a PCollection<KV<String, B>>?

I'm trying to use a custom Coder so that I can do some transforms, but I'm having trouble getting the PCollection to use my custom coder, and I suspect (???) it's because it's wrapped in a KV. Specifically:
Pipeline p = Pipeline.create ...
p.getCoderRegistry().registerCoder(MyClass.class, MyClassCoder.class);
...
PCollection<String> input = ...
PCollection<KV<String, MyClass>> t = input.apply(new ToKVTransform());
When I try to run something like this, I get a java.lang.ClassCastException and a stacktrace that includes a SerializableCoder instead of MyClassCoder like I would expect.
[error] at com.google.cloud.dataflow.sdk.coders.SerializableCoder.decode(SerializableCoder.java:133)
[error] at com.google.cloud.dataflow.sdk.coders.SerializableCoder.decode(SerializableCoder.java:50)
[error] at com.google.cloud.dataflow.sdk.coders.KvCoder.decode(KvCoder.java:95)
[error] at com.google.cloud.dataflow.sdk.coders.KvCoder.decode(KvCoder.java:42)
I see that the answer to another, somewhat related question (Using TextIO.Write with a complicated PCollection type in Google Cloud Dataflow) says to map everything to strings, and use that to pass stuff around PCollections. Is that really the recommended way??
(Note: the actual code is in Scala, but I'm pretty sure it's not a Scala <=> Java issue so I've translated it into Java here.)
Update to include Scala code and more background:
So this is the actual exception itself (should have included this at the beginning):
java.lang.ClassCastException: cannot assign instance of scala.collection.immutable.HashMap$SerializationProxy to field com.example.schema.Schema.keyTypes of type scala.collection.immutable.Map in instance of com.example.schema.Schema
Where com.example.schema.Schema is:
case class Schema(id: String, keyTypes: Map[String, Type])
And lastly, the SchemaCoder is:
class SchemaCoder extends com.google.cloud.dataflow.sdk.coders.CustomCoder[Schema] {
def decode(inputStream: InputStream, context: Context): Schema = {
val ois = new ObjectInputStream(inputStream)
val id: String = ois.readObject().asInstanceOf[String]
val javaMap: java.util.Map[String, Type] = ois.readObject().asInstanceOf[java.util.Map[String, Type]]
ois.close()
Schema(id, javaMap.asScala.toMap)
}
def encode(schema: Schema, outputStream: OutputStream, context: Context): Unit = {
val baos = new ByteArrayOutputStream()
val oos = new ObjectOutputStream(baos)
oos.writeObject(schema.id)
val javaMap: java.util.Map[String, Type] = schema.keyTypes.asJava
oos.writeObject(javaMap)
oos.close()
val encoded = new String(Base64.encodeBase64(baos.toByteArray()))
outputStream.write(encoded.getBytes())
}
}
====
Edit2: And here's what ToKVTransform actually looks like:
class SchemaExtractorTransform extends PTransform[PCollection[String], PCollection[Schema]] {
class InferSchemaFromStringWithKeyFn extends DoFn[String, KV[String, Schema]] {
override def processElement(c: DoFn[String, KV[String, Schema]]#ProcessContext): Unit = {
val line = c.element()
inferSchemaFromString(line)
}
}
class GetFirstFn extends DoFn[KV[String, java.lang.Iterable[Schema]], Schema] {
override def processElement(c: DoFn[KV[String, java.lang.Iterable[Schema]], Schema]#ProcessContext): Unit = {
val idAndSchemas: KV[String, java.lang.Iterable[Schema]] = c.element()
val it: java.util.Iterator[Schema] = idAndSchemas.getValue().iterator()
c.output(it.next())
}
}
override def apply(inputLines: PCollection[String]): PCollection[Schema] = {
val schemasWithKey: PCollection[KV[String, Schema]] = inputLines.apply(
ParDo.named("InferSchemas").of(new InferSchemaFromStringWithKeyFn())
)
val keyed: PCollection[KV[String, java.lang.Iterable[Schema]]] = schemasWithKey.apply(
GroupByKey.create()
)
val schemasOnly: PCollection[Schema] = keyed.apply(
ParDo.named("GetFirst").of(new GetFirstFn())
)
schemasOnly
}
}
This problem doesn't reproduce in Java; Scala is doing something differently with types that breaks Dataflow coder inference. To work around this, you can call setCoder on a PCollection to set its Coder explicitly, such as
schemasWithKey.setCoder(KvCoder.of(StringUtf8Coder.of(), SchemaCoder.of());
Here's the Java version of your code, just to make sure that it's doing approximately the same thing:
public static class SchemaExtractorTransform
extends PTransform<PCollection<String>, PCollection<Schema>> {
class InferSchemaFromStringWithKeyFn extends DoFn<String, KV<String, Schema>> {
public void processElement(ProcessContext c) {
c.output(KV.of(c.element(), new Schema()));
}
}
class GetFirstFn extends DoFn<KV<String, java.lang.Iterable<Schema>>, Schema> {
private static final long serialVersionUID = 0;
public void processElement(ProcessContext c) {
c.output(c.element().getValue().iterator().next());
}
}
public PCollection<Schema> apply(PCollection<String> inputLines) {
PCollection<KV<String, Schema>> schemasWithKey = inputLines.apply(
ParDo.named("InferSchemas").of(new InferSchemaFromStringWithKeyFn()));
PCollection<KV<String, java.lang.Iterable<Schema>>> keyed =
schemasWithKey.apply(GroupByKey.<String, Schema>create());
PCollection<Schema> schemasOnly =
keyed.apply(ParDo.named("GetFirst").of(new GetFirstFn()));
return schemasOnly;
}
}

Creating an instance of a generic type in DART

I was wondering if is possible to create an instance of a generic type in Dart. In other languages like Java you could work around this using reflection, but I'm not sure if this is possible in Dart.
I have this class:
class GenericController <T extends RequestHandler> {
void processRequest() {
T t = new T(); // ERROR
}
}
I tried mezonis approach with the Activator and it works. But it is an expensive approach as it uses mirrors, which requires you to use "mirrorsUsed" if you don't want to have a 2-4MB js file.
This morning I had the idea to use a generic typedef as generator and thus get rid of reflection:
You define a method type like this: (Add params if necessary)
typedef S ItemCreator<S>();
or even better:
typedef ItemCreator<S> = S Function();
Then in the class that needs to create the new instances:
class PagedListData<T>{
...
ItemCreator<T> creator;
PagedListData(ItemCreator<T> this.creator) {
}
void performMagic() {
T item = creator();
...
}
}
Then you can instantiate the PagedList like this:
PagedListData<UserListItem> users
= new PagedListData<UserListItem>(()=> new UserListItem());
You don't lose the advantage of using generic because at declaration time you need to provide the target class anyway, so defining the creator method doesn't hurt.
You can use similar code:
import "dart:mirrors";
void main() {
var controller = new GenericController<Foo>();
controller.processRequest();
}
class GenericController<T extends RequestHandler> {
void processRequest() {
//T t = new T();
T t = Activator.createInstance(T);
t.tellAboutHimself();
}
}
class Foo extends RequestHandler {
void tellAboutHimself() {
print("Hello, I am 'Foo'");
}
}
abstract class RequestHandler {
void tellAboutHimself();
}
class Activator {
static createInstance(Type type, [Symbol constructor, List
arguments, Map<Symbol, dynamic> namedArguments]) {
if (type == null) {
throw new ArgumentError("type: $type");
}
if (constructor == null) {
constructor = const Symbol("");
}
if (arguments == null) {
arguments = const [];
}
var typeMirror = reflectType(type);
if (typeMirror is ClassMirror) {
return typeMirror.newInstance(constructor, arguments,
namedArguments).reflectee;
} else {
throw new ArgumentError("Cannot create the instance of the type '$type'.");
}
}
}
I don't know if this is still useful to anyone. But I have found an easy workaround. In the function you want to initialize the type T, pass an extra argument of type T Function(). This function should return an instance of T. Now whenever you want to create object of T, call the function.
class foo<T> {
void foo(T Function() creator) {
final t = creator();
// use t
}
}
P.S. inspired by Patrick's answer
2022 answer
Just came across this problem and found out that although instantiating using T() is still not possible, you can get the constructor of an object easier with SomeClass.new in dart>=2.15.
So what you could do is:
class MyClass<T> {
final T Function() creator;
MyClass(this.creator);
T getGenericInstance() {
return creator();
}
}
and when using it:
final myClass = MyClass<SomeOtherClass>(SomeOtherClass.new)
Nothing different but looks cleaner imo.
Here's my work around for this sad limitation
class RequestHandler {
static final _constructors = {
RequestHandler: () => RequestHandler(),
RequestHandler2: () => RequestHandler2(),
};
static RequestHandler create(Type type) {
return _constructors[type]();
}
}
class RequestHandler2 extends RequestHandler {}
class GenericController<T extends RequestHandler> {
void processRequest() {
//T t = new T(); // ERROR
T t = RequestHandler.create(T);
}
}
test() {
final controller = GenericController<RequestHandler2>();
controller.processRequest();
}
Sorry but as far as I know, a type parameter cannot be used to name a constructor in an instance creation expression in Dart.
Working with FLutter
typedef S ItemCreator<S>();
mixin SharedExtension<T> {
T getSPData(ItemCreator<T> creator) async {
return creator();
}
}
Abc a = sharedObj.getSPData(()=> Abc());
P.S. inspired by Patrick
simple like that.
import 'dart:mirrors';
void main(List<String> args) {
final a = A<B>();
final b1 = a.getInstance();
final b2 = a.getInstance();
print('${b1.value}|${b1.text}|${b1.hashCode}');
print('${b2.value}|${b2.text}|${b2.hashCode}');
}
class A<T extends B> {
static int count = 0;
T getInstance() {
return reflectClass(T).newInstance(
Symbol(''),
['Text ${++count}'],
{Symbol('value'): count},
).reflectee;
}
}
class B {
final int value;
final String text;
B(this.text, {required this.value});
}
Inspired by Patrick's answer, this is the factory I ended up with.
class ServiceFactory<T> {
static final Map<Type, dynamic> _cache = <String, dynamic>{};
static T getInstance<T>(T Function() creator) {
String typeName = T.toString();
return _cache.putIfAbsent(typeName, () => creator());
}
}
Then I would use it like this.
final authClient = ServiceFactory.getInstance<AuthenticationClient>(() => AuthenticationClient());
Warning: Erik made a very good point in the comment below that the same type name can exist in multiple packages and that will cause issues. As much as I dislike to force the user to pass in a string key (that way it's the consumer's responsibility to ensuring the uniqueness of the type name), that might be the only way.

Dynamic class method invocation in Dart

Like the question at Dynamic class method invocation in PHP I want to do this in Dart.
var = "name";
page.${var} = value;
page.save();
Is that possible?
There are several things you can achieve with Mirrors.
Here's an example how to set values of classes and how to call methods dynamically:
import 'dart:mirrors';
class Page {
var name;
method() {
print('called!');
}
}
void main() {
var page = new Page();
var im = reflect(page);
// Set values.
im.setField("name", "some value").then((temp) => print(page.name));
// Call methods.
im.invoke("method", []);
}
In case you wonder, im is an InstanceMirror, which basically reflects the page instance.
There is also another question: Is there a way to dynamically call a method or set an instance variable in a class in Dart?
You can use Dart Mirror API to do such thing. Mirror API is not fully implemented now but here's how it could work :
import 'dart:mirrors';
class Page {
String name;
}
main() {
final page = new Page();
var value = "value";
InstanceMirror im = reflect(page);
im.setField("name", value).then((_){
print(page.name); // display "value"
});
}
You can use Serializable
For example:
import 'package:serializable/serializable.dart';
#serializable
class Page extends _$PageSerializable {
String name;
}
main() {
final page = new Page();
var attribute = "name";
var value = "value";
page["name"] = value;
page[attribute] = value;
print("page.name: ${page['name']}");
}

Resources