Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serialization Issues #873

Open
Dokotela opened this issue May 4, 2023 · 2 comments
Open

Serialization Issues #873

Dokotela opened this issue May 4, 2023 · 2 comments

Comments

@Dokotela
Copy link

Dokotela commented May 4, 2023

Describe the bug
I'm having trouble using the serialization process for serverpod. Here's the repo if you'd like to take a look: https://github.com/fhir-fli/fhirpod. All of my classes are freezed, and I've adjusted all of the fromJson to include the serializationManagers. That has no errors. When I try to generate code, if I remove json_serializable from the pubspec and all of the part 'file.g.dart'; code, then it generates fine. However, it doesn't generate the toJson() files that are required.

However, when I do include the json_serializable to generate the toJson() methods, I get this error message:

[SEVERE] json_serializable on lib/r4/resource_types/financial/general/general.dart:

Expecting a `fromJson` constructor with exactly one positional parameter. The only extra parameters allowed are functions of the form `T Function(Object?) fromJsonT` where `T` is a type parameter of the target type.
package:fhir/r4/special_types/special_types.dart:483:16

I'm using serverpod_serialization: ^1.1.0.

Just hoping someone else had a similar experience and had found a solution.

@Dokotela
Copy link
Author

Dokotela commented May 12, 2023

So I thought I'd update this. Since I couldn't get the above working, I've decided to try a different tack. Ideally it would just be generated from the classes I already have, but since I can't get that to work, I thought I'd try and pull the data from the class directly into the yaml file for generation. I'm still working out some of the details, but I think it general it's a possibility. I'll attach my script here in case anyone else wants to try and play with it:

import 'dart:io';

Future<void> main() async {
  final Directory dir = Directory('.');
  final List<String> fileList = await dir
      .list(recursive: true)
      .map((FileSystemEntity event) => event.path)
      .toList();
  for (final String file in fileList) {
    if (file.contains('.dart') &&
        !file.contains('.g.') &&
        !file.contains('freezed') &&
        !file.contains('enum')) {
      final String fileString = await File(file).readAsString();
      final List<String> stringList = fileString.split('\n');
      String yaml = '';
      bool isClass = false;
      String className = '';
      for (final String line in stringList) {
        if (isClass) {
          if (line.trim().startsWith('///')) {
          } else if (line.endsWith(',')) {
            final List<String> splitLine = line.split(' ');
            yaml += '  ';
            yaml += changeFieldName(splitLine.last.replaceAll(',', ''));
            yaml += ': ';
            if (changeFieldName(splitLine.last.replaceAll(',', '')) ==
                    'routeOfAdministration' &&
                className == 'AdministrableProductDefinition') {
              yaml += 'AdministrableProductDefinitionRouteOfAdministration';
            } else if (changeFieldName(splitLine.last.replaceAll(',', '')) ==
                    'relatedMedicationKnowledge' &&
                className == 'MedicationKnowledge') {
              yaml += 'MedicationKnowledgeRelatedMedicationKnowledge';
            } else {
              yaml += changeYamlTypes(splitLine[splitLine.length - 2]);
            }

            yaml += '\n';
          } else if (line.contains('}) = _')) {
            isClass = false;
            await File(
                    '../../../fhirpod/fhirpod_server/lib/src/protocol/${className.toLowerCase()}.yaml')
                .writeAsString(yaml);
            yaml = '';
          }
        }
        if (line.contains('factory') && line.contains('({')) {
          isClass = true;
          className =
              line.replaceAll('factory ', '').replaceAll('({', '').trim();
          yaml += 'class: $className\n';
          yaml += 'fields: \n';
        }
      }
    }
  }
  await File('../../../fhirpod/fhirpod_server/lib/src/protocol/resource.yaml')
      .writeAsString(resourceYaml);
}

const String resourceYaml = '''
class: Resource
fields: 
  resourceType: String
  id: String?
  meta: FhirMeta?
  implicitRules: String?
  implicitRulesElement: Element?
  language: String?
  languageElement: Element?
  text: Narrative?
  contained: List<Resource>?
  extension_: List<FhirExtension>?
  modifierExtension: List<FhirExtension>?
''';

String changeYamlTypes(String oldType) {
  final int index = yamlTypes.keys.toList().indexWhere((String element) =>
      element.contains(oldType
          .replaceAll('?', '')
          .replaceAll('List<', '')
          .replaceAll('>', '')));

  if (index == -1) {
    return oldType;
  } else {
    return oldType.replaceAll(
        yamlTypes.keys.elementAt(index), yamlTypes.values.elementAt(index));
  }
}

String changeFieldName(String name) => name
    .replaceAll('productionIdentifierInUDI', 'productionIdentifierInUdi')
    .replaceAll('carrierAIDC', 'carrierAidc')
    .replaceAll('carrierHRF', 'carrierHrf')
    .replaceAll('requestURL', 'requestUrl');

const Map<String, String> yamlTypes = <String, String>{
  'R5ResourceType': 'String',
  'String': 'String',
  'FhirBase64Binary': 'String',
  'FhirBoolean': 'bool',
  'FhirCanonical': 'String',
  'FhirCode': 'String',
  'FhirDate': 'DateTime',
  'FhirTime': 'String',
  'FhirDateTime': 'DateTime',
  'FhirDecimal': 'double',
  'FhirUri': 'String',
  'FhirUrl': 'String',
  'FhirId': 'String',
  'FhirInstant': 'DateTime',
  'FhirInteger': 'int',
  'FhirInteger64': 'BigInt',
  'FhirMarkdown': 'String',
  'FhirOid': 'String',
  'FhirPositiveInt': 'int',
  'FhirUnsignedInt': 'int',
  'FhirUuid': 'String',
  'FhirDuration': 'String',
  'IdentifierUse': 'String',
  'QuantityComparator': 'String',
  'DurationComparator': 'String',
  'DistanceComparator': 'String',
  'CountComparator': 'String',
  'AgeComparator': 'String',
  'HumanNameUse': 'String',
  'AddressUse': 'String',
  'AddressType': 'String',
  'ContactPointSystem': 'String',
  'ContactPointUse': 'String',
  'TimingRepeatDurationUnit': 'String',
  'TimingRepeatPeriodUnit': 'String',
  'TimingRepeatWhen': 'String',
  'ContributorType': 'String',
  'DataRequirementSortDirection': 'String',
  'RelatedArtifactType': 'String',
  'TriggerDefinitionType': 'String',
  'NarrativeStatus': 'String',
  'ElementDefinitionRepresentation': 'String',
  'ElementDefinitionSlicingRules': 'String',
  'ElementDefinitionDiscriminatorType': 'String',
  'ElementDefinitionTypeAggregation': 'String',
  'ElementDefinitionTypeVersioning': 'String',
  'ElementDefinitionConstraintSeverity': 'String',
  'ElementDefinitionBindingStrength': 'String',
};

@Dokotela
Copy link
Author

So some progress today, albeit with a different direction. Or at least, a workaround. First step is to generate all of your code as usual using freezed and jsonserializable. Then, add in the SerializationManager as an optional, positional parameter (this way it doesn't interfere if you call fromJson from any other methods or functions). I did a regex lookup and replace in VSCode. End result looks like this:

// ignore_for_file: invalid_annotation_target
// ignore_for_file: sort_unnamed_constructors_first
// ignore_for_file: sort_constructors_first
// ignore_for_file: avoid_unused_constructor_parameters

// Dart imports:
import 'dart:convert';

// Package imports:
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:serverpod_serialization/serverpod_serialization.dart';
import 'package:yaml/yaml.dart';

// Project imports:
import '../../r5.dart';

part 'element.freezed.dart';
part 'element.g.dart';

@freezed
class Element with _$Element {
  Element._();
  factory Element({
    @JsonKey(name: 'id') FhirId? fhirId,
    @JsonKey(name: 'extension') List<FhirExtension>? extension_,
    @JsonKey(name: 'fhir_comments') List<String>? fhirComments,
  }) = _Element;

  factory Element.fromJson(
    Map<String, dynamic> json, [
    SerializationManager? serializationManager,
  ]) =>
      _$ElementFromJson(json);
  }
}

Again, you can't regenerate your code at this point, it will throw an error. But, you can generate serverpod data. In your serverpod_server/config/generator.yaml file, you can add this class in like this:

type: server

client_package_path: ../fhirpod_client

extraClasses:
  - package:fhir/r5.dart:Element

Then run serverpod generate and it will generate the protocol.dart file for the serializationManager, using your class. Next step is to figure out how to generate table data.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant