smartstruct icon indicating copy to clipboard operation
smartstruct copied to clipboard

Map objectId to object

Open andrew-tpz opened this issue 2 years ago • 4 comments

Question: How to realize this kind of mapping?

class Target {
  final String text1;
  final Sourse2 source2;

  Target(this.text1, this.source2);
}

class Source {
  final String text1;
  final int source2Id;  
  Source(this.text1);
}

class Source2 {
  final int id;
  final String text2;
  Source2(this.text2);
}

"Multiple sources" example is close to that, but not exactly.

andrew-tpz avatar May 20 '22 14:05 andrew-tpz

Hi, I'm not sure I understood correctly, but if you want to map your Source2 into the Target, you can achieve this by adding an explicit Function Mapping like this.

@Mapper()
abstract class TargetMapper {
  @Mapping(target: 'source2', source: mapSource2)
  Target fromSource(Source source, Source2 source2);
}

Source2 mapSource2(Source source, Source2 source2) => source2;

Would generate this mapper.

class TargetMapperImpl extends TargetMapper {
  TargetMapperImpl() : super();

  @override
  Target fromSource(Source source, Source2 source2) {
    final target = Target(source.text1, mapSource2(source, source2));
    return target;
  }
}

smotastic avatar May 21 '22 17:05 smotastic

Hi, yes but no :) I tried this solution before posting the issue, but faced with some problems. Here is the more precise example:

import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:smartstruct/smartstruct.dart';

part 'test_mapper.freezed.dart';
part 'test_mapper.mapper.g.dart';

@freezed
class UserModel with _$UserModel {
  const factory UserModel({
    required int id,
    required int companyId, // we have id only in source
    required String name,
  }) = _UserModel;

  const UserModel._();
}

@freezed
class User with _$User {
  const factory User({
    required int id,
    required Company company, // we need entity in target
    required String name,
  }) = _User;

  const User._();
}

@freezed
class Company with _$Company {
  const factory Company({
    required int id,
    required String name,
  }) = _Company;

  const Company._();
}

@Mapper()
abstract class UserMapper {
  @Mapping(target: 'company', source: _mapCompany)
  User fromModel(UserModel model, Company company);
}

Company _mapCompany(UserModel _, Company company) => company;

Error: Mapper got case insensitive fields and contains fields: id and id. If you use a case-sensitive mapper, make sure the fields are unique in a case insensitive way.

This is because of UserModel and Company both have identical fields names "id" (and "name").

andrew-tpz avatar May 23 '22 09:05 andrew-tpz

Test to reproduce:

part of 'mapper_test_input.dart';

class ObjectTarget {
  final String text;
  final num number;
  final AnotherSource another;

  ObjectTarget(this.text, this.number, this.another);
}

class ObjectSource {
  final String text;
  final num number;
  final num anotherNumber;

  ObjectSource(this.text, this.number, this.anotherNumber);
}

class AnotherSource {
  final String text;
  final num number;

  AnotherSource(this.text, this.number);
}

@Mapper()
@ShouldGenerate(r'''
class ObjectMapperImpl extends ObjectMapper {
  ObjectMapperImpl() : super();

  @override
  ObjectTarget fromSource(ObjectSource source, AnotherSource another) {
    final objecttarget = ObjectTarget(
        source.text, source.number, _mapAnother(source, another));
    return objecttarget;
  }
}
''')
abstract class ObjectMapper {
  @Mapping(target: 'another', source: _mapAnother)
  ObjectTarget fromSource(ObjectSource source, AnotherSource another);
}

AnotherSource _mapAnother(ObjectSource source, AnotherSource another) =>
    another;

andrew-tpz avatar May 24 '22 12:05 andrew-tpz

Yes, that's a usecase I didn't think about.

Right now I can ignore certain fields, but not by their source param. So one solution would be to be able to ignore a field by it's source param, such as.

class IdSource {
  final num id;
  IdSource(this.id);
}

class IdTarget1 {
  final num id;
  IdTarget1(this.id);
}

class IdTarget2 {
  final num id;
  IdTarget2(this.id);
}

@Mapper()
abstract class IdMapper {
  @Mapping(target: 'idTarget2.id', ignore: true)
  IdSource fromTarget(IdTarget1 idTarget1, IdTarget2 idTarget2);
}

This way it would only map the id of IdTarget1. Unfortunately this is not possible at the moment.

smotastic avatar Jun 04 '22 05:06 smotastic