Giter Club home page Giter Club logo

Comments (17)

tnc1997 avatar tnc1997 commented on September 23, 2024 1

Hi @Zekfad, thank you for your interest in this package. When the xml_serializable package was created it was intended to match the behaviour of the json_serializable package as closely as possible, however I agree that there is a lot more boilerplate required for XML. I will aim to further investigate the use of mixins in order to reduce the amount of boilerplate this week.

from dart-xml-serializable.

tnc1997 avatar tnc1997 commented on September 23, 2024 1

Hi @Zekfad, thank you again for your feature request. Having investigated further, I'm not sure how I feel about using a generic mixin and parser classes for the build... and to... methods, because in my opinion it trades off boilerplate code for slightly more complexity in the class being serialized with the inclusion of the mixin and the _parser. I also think that the _parser would need to be public if the intention is for the mixin to be exported from the package rather than the class being serialized. A potential alternative to reduce the amount of boilerplate required that I quite like might be to move the build... and to... methods into a generated extension on the class being serialized like this. I would be interested to know what you think of this proposal.

from dart-xml-serializable.

tnc1997 avatar tnc1997 commented on September 23, 2024 1

If you want to import a single class, you'll always need to explicitly import extension

Apologies, I'm having difficulty reproducing this locally, I've updated the branch with a sample that seems to work.

// This works.
import 'xml_serializable_example.dart';
// This does not.
// import 'xml_serializable_example.dart' show Book;

void main(List<String> arguments) {
  // This works.
  final book = Book();
  book.toXmlElement();

  // This does not.
  // dynamic book = Book();
  // book.toXmlElement();
}

EDIT: I see what you mean now, if only the Book class is imported, then it won't also import the extensions, my bad!

from dart-xml-serializable.

Zekfad avatar Zekfad commented on September 23, 2024 1

#13 (comment)

I meant that following likely must be done by hand:
import 'xml_serializable_example.dart' show Book, BookXmlSerializableExtensions; -> import 'xml_serializable_example.dart' show BookTest, BookTestXmlSerializableExtensions;
Notice BookTestXmlSerializableExtensions likely wont be renamed with refactor.

from dart-xml-serializable.

tnc1997 avatar tnc1997 commented on September 23, 2024 1

Whilst I agree that the approach using both a generic mixin and a generic abstract class (interface) offers benefits, in my opinion it introduces quite a bit of complexity that not everyone might be comfortable with. It would also require a major release of this package as it would be a breaking change. I have updated the branch, converting the extensions to mixins in order to overcome some of the obstacles that you mentioned such as imports and dynamics, whilst still keeping it fairly straightforward. In theory I think that it would still be possible for anyone desiring the extra benefits of an abstract class (interface) to add this to their project. I would be interested to know what you think of this approach instead of the extension based approach.

from dart-xml-serializable.

Zekfad avatar Zekfad commented on September 23, 2024 1

Hi @tnc1997, thanks for reply. This looks good to me, you can even add something like that:

import 'package:xml/xml.dart';

// Name subject to change
abstract class XmlSerializableClass {
  void buildXmlChildren(
    XmlBuilder builder, {
    Map<String, String> namespaces = const {},
  });

  void buildXmlElement(
    XmlBuilder builder, {
    Map<String, String> namespaces = const {},
  });

  List<XmlAttribute> toXmlAttributes({
    Map<String, String?> namespaces = const {},
  });

  List<XmlNode> toXmlChildren({
    Map<String, String?> namespaces = const {},
  });

  XmlElement toXmlElement({
    Map<String, String?> namespaces = const {},
  });
}

And make changes to mixins like this:

// ...
mixin BookXmlSerializableMixin implements XmlSerializableClass {
// ...

After that, something like this will be possible:

  final XmlSerializableClass book = Book();
  book.buildXmlChildren(XmlBuilder());

That's indeed good solution, I think.

Note: I'm not so good at naming, so my versions are: XmlSerializableInterface, XmlSerializableClass, XmlSerializable (already taken), XmlSerializableObject.

from dart-xml-serializable.

tnc1997 avatar tnc1997 commented on September 23, 2024 1

That's indeed good solution, I think.

Happy days, in that case I will start work adding an option to use mixins instead of creating build... and to... methods in each class manually as per the example. In order to maintain backwards compatibility I will probably introduce this as opt-in initially. Thank you again for providing feedback and helping to architect the solution. I will keep this issue open until the changes have been released to https://pub.dev and comment here if there are any unforeseen issues during the development process.

from dart-xml-serializable.

tnc1997 avatar tnc1997 commented on September 23, 2024 1

but maybe a completely different API still would require major version bump rather than being an optional feature

I would like to avoid a completely different API ideally, because whilst possible it introduces a lot of maintenance overhead.

I'd really love to see a way of splitting classes (in my use case I'll have to write about 17 different elements)

Apologies if I'm misunderstanding, but if you're referring to the ability to create one class per file, then that is possible in the current version without an interface and I intend to retain this behaviour. I have updated the branch to show a code sample.

from dart-xml-serializable.

tnc1997 avatar tnc1997 commented on September 23, 2024 1

Any progress on this?

Hi @Zekfad, apologies but there hasn't been much progress yet. I'm currently in the process of making internal changes to the package in order to improve maintainability and support unit tests, before working on adding new features and functionality. That being said, I'm aiming to have a branch with mixin support available for you to test within the next couple of weeks if everything goes to plan.

from dart-xml-serializable.

Zekfad avatar Zekfad commented on September 23, 2024

Hello @tnc1997, thank for you reply. Your approach with extensions, looks good, but has two problems. If you want to import a single class, you'll always need to explicitly import extension (using example from your branch):

import 'xml_serializable_example.dart' show Book, BookXmlSerializableExtensions;
void main(List<String> arguments) async {
  Book book = Book();
  book.toXmlAttributes();
}

Notice, that without explicitly importing BookXmlSerializableExtensions it wont work, also, you wont be able to easily rename the class with a single hit of refactor (F2 in most IDE), because BookXmlSerializableExtensions has class name hardcoded within.
Also extensions doesn't work with dynamic, so this will fail:

  dynamic book = Book();
  book.toXmlAttributes();
NoSuchMethodError (NoSuchMethodError: Class 'Book' has no instance method 'toXmlAttributes'.
Receiver: Instance of 'Book'
Tried calling: toXmlAttributes())

Just aside from that, it would be nice to have some sort of interface (in terms of dart it is an abstract class, we can import and use implements with it) for all serializable classed, but that's can be done on userland.

from dart-xml-serializable.

Zekfad avatar Zekfad commented on September 23, 2024

Adding more complete example:
Using mixins first 2 lines are ok, but the last aren't:

import 'xml_serializable_example.dart' show Book, BookXmlSerializableExtensions;
import 'package:xml/xml.dart';
import 'test.dart' show Person; // code from first post

void main(List<String> arguments) async {
  dynamic person = Person();
  person.buildXmlChildren(XmlBuilder());

  dynamic book = Book();
  book.buildXmlChildren(XmlBuilder());
}

Regarding _parser, yes, it must be public parser instead, otherwise we cant move XMLParserMixin into different file, that's my mistake.
Because parser is quite common name, we can make it something like xmlSerializableParserInstance, to remove possible collisions.

from dart-xml-serializable.

tnc1997 avatar tnc1997 commented on September 23, 2024

you wont be able to easily rename the class with a single hit of refactor

In theory running build_runner should handle leftover mentions of the old name in the same way as json_serializable:

factory BookTest.fromXmlElement(XmlElement element) => _$BookFromXmlElement(element); ->
factory BookTest.fromXmlElement(XmlElement element) => _$BookTestFromXmlElement(element);

extension BookXmlSerializableExtensions on BookTest ->
extension BookTestXmlSerializableExtensions on BookTest

I think that this would be the same as in the parser approach where the parser _$PersonParser contains the class name.

from dart-xml-serializable.

Zekfad avatar Zekfad commented on September 23, 2024

With little more changes here's additional benefit (generic interface):
AuxCode.dart (lib):

import 'package:xml/xml.dart';

typedef XMLSerializableClass<T> = XMLParserMixin<T, XMLSerializableParser<T>>;

abstract class XMLSerializableParser<T> {
  const XMLSerializableParser();

  T fromXmlElement(XmlElement element);
  void buildXmlChildren(
    T instance,
    XmlBuilder builder, {
    Map<String, String> namespaces = const {},
  });
  // and so on...
}

mixin XMLParserMixin<T, Parser extends XMLSerializableParser<T>> {
  Parser get xmlSerializableParser;

  void buildXmlChildren(
    XmlBuilder builder, {
    Map<String, String> namespaces = const {},
  }) =>
    xmlSerializableParser.buildXmlChildren(
      this as T,
      builder,
      namespaces: namespaces,
    );
  // and so on...
}

person.dart:

import 'package:xml/xml.dart';
import 'AuxCode.dart';

part 'person.g.dart';

/// File code
class Person with XMLSerializableClass<Person> {
// or full form:
//class Person with XMLParserMixin<Person, _$PersonParser> {
  static const _personParser = _$PersonParser();

  // we can use directly generated class type, but also generic one
  // to reduce dependency on generated names and less work when we rename class
  @override
  XMLSerializableParser<Person> get xmlSerializableParser => _personParser;
  //_$PersonParser get xmlSerializableParser => _personParser;

  String? name;

  Person({
    this.name,
  });

  factory Person.fromXmlElement(XmlElement element) =>
    _personParser.fromXmlElement(element);
  
  // Other methods are implemented via mixin
}

person.g.dart (supposed to be generated):

part of 'person.dart';

class _$PersonParser extends XMLSerializableParser<Person> {
  const _$PersonParser() : super();

  @override
  Person fromXmlElement(XmlElement element) {
    print('called fromXmlElement of _\$PersonParser');
    return Person();
  }

  @override
  void buildXmlChildren(
    Person instance,
    XmlBuilder builder, {
    Map<String, String> namespaces = const {},
  }) {
    print('called buildXmlChildren of _\$PersonParser');
  }
}

main.dart:

import 'package:xml/xml.dart';
import 'AuxCode.dart' show XMLSerializableClass;
import 'person.dart' show Person;

void main(List<String> arguments) async {
  XMLSerializableClass<Person> person = Person();

  person.buildXmlChildren(XmlBuilder());
  someGenericFunctionWhichNeedXMLSerializableObject(person);

  Person person2 = Person();
  person2.buildXmlChildren(XmlBuilder());
  someGenericFunctionWhichNeedXMLSerializableObject(person2);
}

void someGenericFunctionWhichNeedXMLSerializableObject<T extends XMLSerializableClass<T>>(T instance) {
  print('Calling generic function with known interface');
  instance.buildXmlChildren(XmlBuilder());
}

EDIT: Changed some file code, as it turns out we can use more generic approach to keep less generated names in main file.

from dart-xml-serializable.

Zekfad avatar Zekfad commented on September 23, 2024

@tnc1997, speaking of interface, because of it you can split classes across different files, because you can guarantee existence of required for in-depth generation methods.
In your example, we can extract all classes into separated files, (I guess this will need additional annotation, to mark properties which are external xml serializable classes) and inside of mixins you can assign such properties to interface type.
For example:

mixin BookXmlSerializableMixin implements XmlSerializableClass {
  void buildXmlChildren(
    XmlBuilder builder, {
    Map<String, String> namespaces = const {},
  }) {
    // was: final title = (this as Book).title;
    final XmlSerializableClass? title = (this as Book).title;
    //...

So we'll have even better end user API, because it will be possible to split different classes into different files.

from dart-xml-serializable.

Zekfad avatar Zekfad commented on September 23, 2024

That's good to hear!
What about #13 (comment), I'd really love to see a way of splitting classes (in my use case I'll have to write about 17 different elements, it would be messy to put them all into a single file)?
That's of course not for me to decide, but maybe a completely different API still would require major version bump rather than being an optional feature. AFAIK pub.dev allows (not sure) to publish lower version, so it's possible to maintain old version from bugs within a separate branch, similar as npm has tags,

from dart-xml-serializable.

Zekfad avatar Zekfad commented on September 23, 2024

Any progress on this?

from dart-xml-serializable.

tnc1997 avatar tnc1997 commented on September 23, 2024

Hi @Zekfad, apologies for the delay, but I have some good news. There is now a preview branch available to try out the new mixin functionality. The branch can be found at feat/13, it would be great if you could give it a try and let me know of any issues.

dependencies:
  xml_annotation:
    git:
      path: xml_annotation
      ref: feat/13
      url: https://github.com/tnc1997/dart-xml-serializable.git

dev_dependencies:
  xml_serializable:
    git:
      path: xml_serializable
      ref: feat/13
      url: https://github.com/tnc1997/dart-xml-serializable.git

from dart-xml-serializable.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.