I have a model, Post, that is abstract. I have 3 child classes, call them XPost, YPost, and ZPost.
Post looks like
abstract class Post {
final String userUid;
final String documentId;
final String type;
}
and let's say XPost has an additional variable called "data". My question is, how can I access the data variable by creating a Post object.
Post post;
post = await _database.getPost(postID);
print(post.data); //it says data isn't defined for post class
I want to keep the post as the abstract type so I can decide at runtime which type of post (X, Y, Z) it is.
Method getPost returns Post so without casting, you're not going to be able to access properties on inherited classes.
If you expect different types of post to be returned by getPost you could do something like this
switch (post.runtimeType) {
case XPost:
print((post as XPost).data);
case YPost:
print((post as YPost).someotherfield);
}
Related
I want to declare, but not define a factory constructor in an abstract class.
In my case, I want to create a method that accepts any class that implements a String toJson() method as well as a fromJson(Map<String, dynamic> data) factory constructor.
Is there any way to achieve that in Dart?
I'm looking for something like the following, which is not valid Dart code:
abstract class JsonSerializable {
factory fromJson(Map<String, dynamic> data);
String toJson();
}
I'm afraid that it doesn't work the way you want it to.
Constructors are not part of an interface. They act more like static members.
So, you can't add a factory to the interface, and code wouldn't have any way to call the factory constructor given a type variable extending this type anyway.
So, since constructors cannot be part of interfaces, constructors also cannot be abstract. Being abstract simply means "make the member part of the interface, but no implementation is added to class".
You can declare the factory as a normal method, but then you would only be able to call it when you already have an instance, which likely isn't what you want with a constructor.
The only way to pass code around is as functions or objects with methods. So, if you want to parameterize something by a type which is JsonSerializable, and you want to be able to create such an object, you need to pass a factory function along:
T deserialize<T extends JsonSerializable>(
String json,
T factory(Map<String, dynamic> data),
) {
return factory(jsonDecode(json) as Map<String, dynamic>);
}
You an then call it with:
var myValue = deserialize(jsonString, (x) => MyClass.fromJson(x));
(If MyClass.fromJson had been a static function instead of a constructor, you could just write deserialize(jsonString, MyClass.fromJson), but Dart doesn't yet have constructor tear-offs).
As suggested in the accepted answer, I ended up creating a Serializer<T> type that got implemented by a serializer for each class:
Turns out, this has several benefits over just having toJson/fromJson on the classes directly:
It decouples the serialization logic from the actual classes. That means better code readability because classes only contain methods that relate directly to the class — serializers can even be put into their own files.
Currently, extensions can't create constructors. So having serializers separately makes it possible to write serializers for existing classes, like String or Flutter's Color, where you can't just add a fromColor constructor.
Both these points combined mean it also works well with code generation — the classes are hand-written and the serializer can be generated in a separate file.
Code example:
class Fruit {
Fruit(this.name, this.color);
final String name;
final String color;
}
// in another file
class FruitSerializer extends Serializer<Fruit> {
Map<String, dynamic> toJson(Fruit fruit) {
return ...;
}
Fruit fromJson(Map<String, dynamic> data) {
return Fruit(...);
}
}
An then also pass the serializer to the code that needs it:
someMethod<T>(Serializer<T> serializer, T value) {
...
}
someMethod(FruitSerializer(), someFruit);
final fruit = recreateFruit(FruitSerializer());
Obviously, you can't pass an object that can't be serialized to the code, because the method expects a Serializer<T>.
I am using Grails 3.3.9 and json views 1.2.10.
In another test I have a url mapping like this
"/api/test"(resources:'test')
And I have an empty views/test folder, and I don't have a Test domain object.
But I do have a Device domain entity, and have a views/device/_device.gson template in that directory to help render a device object.
The TestController's show action looks like this
def show (Device device) {
if(device == null) {
render status:404
} else {
respond device}
}
This looks for a Device in domain model and populates into Shows parameter list, and I respond with the device.
There is however no show.gson in the views/test directory (it's empty).
However when you hit the url in the browser "http://localhost:8080/api/test/1", grails renders a response using the "views/device/_device.gson" template.
This is very confusing as it's very hard to figure exactly view view respond is actually using (unless you force it using a view:'xxx' map variable.
How/why is the TestController/show action response return, picking up "views/device/_device.gson" template ?
(Posted answer on behalf of the question author).
Step 1) if you create a new controller (I used grails create-controller <controller>, and then edit the generated controller to extend from RestfulController<domain type> (but don't override any methods). Then Setup the URL mapping for your controller, in my example this was
"/api/org"(resources:'OrgRoleInstance')
for which I have the corresponding domain class 'OrgRoleInstance' in my model.
The create-controller action also generates an empty view package 'views/orgRoleInstance' (no gson files are generated)
a) if you do the run app now and open the url
http://localhost:8080/api/org
then your still get a rendered response (like this) !
[{"id":1,"sites":[],"domains":[],"role":{"enumType":"com.softwood.domain.OrgRoleInstance$OrgRoleType","name":"Service_Provider"},"name":"Vodafone","mags":[{"id":2}]},{"id":2,"sites":[],"domains":[],"role":{"enumType":"com.softwood.domain.OrgRoleInstance$OrgRoleType","name":"Maintainer"},"name":"Cisco","mags":[{"id":1}]},{"id":3,"sites":[],"domains":[],"role":{"enumType":"com.softwood.domain.OrgRoleInstance$OrgRoleType","name":"Supplier"},"name":"Cisco","mags":[]},{"id":4,"sites":[{"id":1},{"id":2}],"domains":[{"id":1}],"role":{"enumType":"com.softwood.domain.OrgRoleInstance$OrgRoleType","name":"Customer"},"name":"Acme","mags":[]}]
Ipso facto there is some 'default framework logic' that tries to render the default implementation of the 'index' (and 'show' etc) action inherited from RestfulController'. This is done even with with no pre-existing gson - no warnings are provided.
Step 2: next stop the app (it's still caching the previous 'default' behaviour).
Now go to the empty view directory and create an 'index.gson' like this with no model
json ([1,2,3])
Then restart the app in your IDE. Now when it starts, it detects that an index.gson exists, and when you call the url 'http://localhost:8080/api/org' what you get is the rendered json for the static list [1,2,3]
step 3: gson views are using static compilation under the covers and very fiddly to get exactly right. However in the case of default RestfulController index action , a list of OrgRoleInstances is selected from the domain model and passed as a lit to the view. if you want the databinding to the view to work, then if your domain object is type T, the you get a List<T> returned.
The internal default in the code base, is if you get List<T> returned to respond then, the data model in the view is assumed to be List<T> <T>List i.e. in my example this would be
List<OrgRoleInstance> orgRoleInstanceList in the gson view model. Now with a revised index.gson (you can edit this without stop/start of server in dev mode)
import com.softwood.domain.OrgRoleInstance
model {
List<OrgRoleInstance> orgRoleInstanceList
}
json {
recordCount orgRoleInstanceList.size()
}
Now when you Get the URL you get a json response with the size of the list:
{"recordCount":4}
Note that if you add an extra variable to your model like this:
import com.softwood.domain.OrgRoleInstance
model {
List<OrgRoleInstance> orgRoleInstanceList
Integer orgRoleInstanceCount
}
json {
recordCount orgRoleInstanceList.size()
size orgRoleInstanceCount
}
and modify the index.gson to print the new Integer variable - then it is not data bound from the default respond action.
Browser response looks like this:
{"recordCount":4,"size":null}
Option 4: - look at invoking a 'template' class. Having set up a model that's expecting a List<T>, the runTime type built via data binding is an instance of "grails.orm.PagedResultList". This is an Iterable type.
This is very confusing to read in the documentation - it's really not clear however if you create a file called _<file>.gson. Then this is treated is a template gson file.
This can have its own model/json, but because all this is statically compiled you have to get the types exactly matching and where necessary declare the 'name' of the model variable when calling the template (either via g.render or implicit tmpl.<file>.
When you call the template from the parent view you can pass either an the List<T> iterable model type or iterate in the parent view and pass each <T> to the template. You have to ensure that the model type you pass to the tmpl is declared as the same type in the tmpl model.
e.g. assuming a List<T> in the parent view but a <T> in the template, you need to invoke the tmpl for each item in the list. e.g.
if you have a tmpl like this "_orgRoleInstance.gson".
If you have the parent view like this (note single <T> declared and model variable named 'org'
import com.softwood.domain.OrgRoleInstance
model {
OrgRoleInstance org
}
json {
id org.id
name org.name
}
Then the parent view "index.gson" you need something like this that invokes the tmpl as many times as you have entries in the list, but I've had to tell the framework that the tmpl model variable name is 'org' by passing a map. This will render as you expect.
import com.softwood.domain.OrgRoleInstance
model {
List<OrgRoleInstance> orgRoleInstanceList
Integer orgRoleInstanceCount
}
orgRoleInstanceList.each { OrgRoleInstance org ->
json tmpl.orgRoleInstance(org:org)
}
If you declare the tmpl variable to be "def org", it still works but as this is statically typed, the variable is passed as static instance of Object (run time type is correct, but you cant just access the properties as the static type is Object), and it gets hard sorting out the casts required to access the properties.
If you want the parent view to pass the model List<T> variable to the tmpl, you can = but you have to ensure that the model variable in the tmpl is List<T> else the data binding doesn't work.
Now in the template you can invoke the json and iterate over the List
e.g. with a modified tmpl like this
import com.softwood.domain.OrgRoleInstance
model {
//OrgRoleInstance org
List<OrgRoleInstance> orgs
}
json {
id orgs.id
name orgs.name
}
and a revised parent view like this to invoke the tmpl:
import com.softwood.domain.OrgRoleInstance
model {
List<OrgRoleInstance> orgRoleInstanceList
Integer orgRoleInstanceCount
}
/*
orgRoleInstanceList.each { OrgRoleInstance org ->
json tmpl.orgRoleInstance(orgs:org)
}*/
//alternate approach
json tmpl.orgRoleInstance (orgs:orgRoleInstanceList)
What get rendered is this:
{"id":[1,2,3,4],"name":["Vodafone","Cisco","Cisco","Acme"]}
You will note that there is a single json clause so it iterates all the ids first and the all the names second.
If this is not what you want you have to iterate over the list to do each in turn
i.e. a modified tmpl like this will iterate over each entry in turn
import com.softwood.domain.OrgRoleInstance
model {
//OrgRoleInstance org
List<OrgRoleInstance> orgs
}
json (orgs) {OrgRoleInstance org ->
id org.id
name org.name
}
and produce this in the browser
[{"id":1,"name":"Vodafone"},{"id":2,"name":"Cisco"},{"id":3,"name":"Cisco"},{"id":4,"name":"Acme"}]
For anyone else who is confused by Grails Views, I hope this shows how this works between url mappings (you can use the Gradle "urlMappingsReport" to see these) and shows what urls are mapped to what actions. You then need gson views created with the same name as these actions, and configure your gson views and any tmpl you create to be aware of the implicit behaviour that the JsonViews applies when processing your code.
Suppose I have Employee domain class, I want to create object of domain class from params map coming from UI side.
I can create object in two ways as follows
Normal way
Employee employee = new Employee(name: params.name, rollNo:
params.rollNo)
and so on. If domain class has 20 variables, then we need to write all variables in above constructor.
Following is best way to create object
Employee employee = new Employee(params)
Above constructor will populate object with matching params. Right.
Now my question comes here.
If suppose I have existing domain class object fetched from DB, Now I want to update this object from params map coming from UI.
What is best way to do this (like we do in above second option).
I think it is best to use command objects and bind it to the Employee.
here is sample pseudo code:
class EmployeeMgmtController {
def editEmp(EmployeeCmd cmd){
Employee editEmp = Employee.get(1)
editEmp.properties = cmd
editEmp.save()
}
}
class EmployeeCmd{
String id
static constraints = {
id blank:false,nullable:false
}
}
or,
you if your on controller, and still want to use params (and exclude any fields that you don't want to bind):
bindData(editEmp, params, [exclude:['firstName', 'lastName']])
If you want to achieve that in a service class, make your service implement grails.web.databinding.DataBinder then use the bindData method as demonstrated below.
import grails.web.databinding.DataBinder
class MyAwesomeService implements DataBinder {
/**
* Updates the given instance of a domain class to have attribute values specified
* in "newData" map.
*/
MyDomain updateMyDomainAttributes(MyDomain myDomianInstance, Map newData) {
bindData(myDomianInstance, newData)
myDomianInstance.save(flush: true)
}
}
I am doing a project which uses httpie. I was able to send primitive fields and it's array as a request query parameter. For example, say i have variable named "age" which is integer type, i can send this variable as a Query parameter, Form parameter, or Path parameter. If i want to send the age as a Query parameter, i would write the following:
http -v -f POST 'localhost:8080/api/v1/public/users?age=20'
The problem is when i want to send an object of non-primitive class. For example, i have a class named Name which is following:
public class Name {
String first, middle, last;
}
Now for an object of this class how can i send an object of Name class as a parameter in httpie request. I tried a lot but didn't find any solution.
Aside from there being less code to write, what are the advantages? Is it more secure?
public JsonResult Update (int id, string name)
{
Person person = new Person{
ID=id,
Name=name
}
SavePerson(person);
return Json(...);
}
OR
public JsonResult Update (Person person)
{
SavePerson(person);
return Json(...);
}
I have to disagree with Nick on the notion of values ending up in the URL string. In fact, there is no difference. Try it! Query string parameters can supply the model values with either method.
Another difference which is possibly significant is that when passing ID and name as arguments, those are the only two fields which can ever be updated. When passing a Person as an argument, potentially other fields could be updated. This may or may not be what you want. But UpdateModel will accept a whitelist of properties you'd like it to update (and similarly for binding a Person instance in an argument), so as long as you remember to consider including a whitelist, there is no real difference here.
To me the biggest difference between the two options you show is who is instantiating the Person instance. When you pass ID and name as arguments, it will always be your controller code which instantiates the Person. When you pass a Person as an argument, it will always be the model binder which instantiates the Person. This could be significant if, rather than instantiating a new Person instance, you would like to materialize an existing instance from a repository.
When using the (int id, string name) approach, it is possible these values would end up in the URL string. The other way, it won't. So, that could possibly be considered more secure.
Other than that, if you change the possible properties in your Person class, you wouldn't have to update the values passed in to the (int id, string name) approach. Although, you can get around this by using UpdateMethod(myPersonInstance).