dartjson-annotation

JsonConverter has no effect on compiled fromJson


I'm trying to create a custom JsonConverter for a specific field on my class. The reason for this is that the API I am retrieving data from has a meta key that either returns a dictionary or an empty array, but should always be saved as Map<String, dynamic>.

My code:

import 'package:json_annotation/json_annotation.dart';

part 'tag.g.dart';

@JsonSerializable()
class Tag {
  int? id;
  int? count;
  String? description;
  Uri? link;
  String? name;
  String? slug; 
  String? taxonomy;
  @EmptyMetaToMapConverter()
  Map<String, dynamic>? meta;

  Tag();

  factory Tag.fromJson(Map<String, dynamic> json) => _$TagFromJson(json);
  Map<String, dynamic> toJson() => _$TagToJson(this);
}

class EmptyMetaToMapConverter extends JsonConverter<Map, Object?> {
  const EmptyMetaToMapConverter();

  @override
  Map<String, dynamic> fromJson(Object? json) {
    if (json is List) return {};
    if (json is Map<String, dynamic>) return json;

    throw Exception();
  }

  @override
  Map toJson(Map object) => object;
}

When I run flutter pub run build_runner build, the corresponding tag.g.dart file is as follows:

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'tag.dart';

// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************

Tag _$TagFromJson(Map<String, dynamic> json) => Tag()
  ..id = json['id'] as int?
  ..count = json['count'] as int?
  ..description = json['description'] as String?
  ..link = json['link'] == null ? null : Uri.parse(json['link'] as String)
  ..name = json['name'] as String?
  ..slug = json['slug'] as String?
  ..taxonomy = json['taxonomy'] as String?
  ..meta = json['meta'] as Map<String, dynamic>?; // The custom converter seems to have no effect here

Map<String, dynamic> _$TagToJson(Tag instance) => <String, dynamic>{
      'id': instance.id,
      'count': instance.count,
      'description': instance.description,
      'link': instance.link?.toString(),
      'name': instance.name,
      'slug': instance.slug,
      'taxonomy': instance.taxonomy,
      'meta': instance.meta,
    };

When I try to deserialize a JSON object with a meta field containing an empty list, it raises an error. What am I doing wrong?

Flutter version: 3.16.8

  json_serializable: ^6.7.1
  build_runner: ^2.4.8
  json_annotation: ^4.8.1

Solution

  • Your custom converter isn't being used because it's using the wrong type of Map. Map implicitly means Map<dynamic, dynamic>, while the map that you need is Map<String, dynamic>. There are 3 places where you need to change it:

    class EmptyMetaToMapConverter extends JsonConverter<Map<String, dynamic>, Object?> { // (1)
      const EmptyMetaToMapConverter();
    
      @override
      Map<String, dynamic> fromJson(Object? json) {
        if (json is List) return {};
        if (json is Map<String, dynamic>) return json;
    
        throw Exception();
      }
    
      @override
      Map<String, dynamic> toJson(Map<String, dynamic> object) => object; // (2) and (3)
    }