Skip to content

Unable to skip empty List when serializing JSON with JsonConverter #1569

@NoBrainer

Description

@NoBrainer

Context

I got my desired behavior to work with @JsonKey, but I couldn't get it to work with a JsonConverter. (I think this should be a solution to a related issue, but it doesn't work as I expect.)

Here is my source code:

// my_model.dart

import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';

part 'my_model.g.dart';

@JsonSerializable(
  explicitToJson: true,
  converters: [
    StringTrimConverter(),
    IgnoreEmptyListConverter(),
    IgnoreEmptyNullableListConverter(),
  ],
  includeIfNull: false,
)
class MyModel extends Equatable {
  final List<int> intList;
  final List<String> strList;
  final String str;
  final String? strOptional;

  @JsonKey(toJson: listToJson)
  final List<int> intListWithJsonKey;

  const MyModel({
    this.intList = const [],
    this.intListWithJsonKey = const [],
    this.strList = const [],
    this.str = '',
    this.strOptional = '',
  });

  @override
  List<Object?> get props => [
    intList,
    intListWithJsonKey,
    strList,
    str,
    strOptional,
  ];

  factory MyModel.fromJson(Map<String, dynamic> json) =>
      _$MyModelFromJson(json);

  Map<String, dynamic> toJson() => _$MyModelToJson(this);
}

List? listToJson(List list) => list.isEmpty ? null : list;

/// Ensure Strings are trimmed when serialized and deserialized.
class StringTrimConverter implements JsonConverter<String, String> {
  const StringTrimConverter();

  @override
  String fromJson(String json) => json.trim();

  @override
  String toJson(String str) => str.trim();
}

/// Ensure list keys are skipped when serializing JSON
class IgnoreEmptyListConverter implements JsonConverter<List, List?> {
  const IgnoreEmptyListConverter();

  @override
  List fromJson(List? json) => json ?? [];

  @override
  List? toJson(List list) => list.isEmpty ? null : list;
}

/// Ensure list keys are skipped when serializing JSON
class IgnoreEmptyNullableListConverter implements JsonConverter<List?, List?> {
  const IgnoreEmptyNullableListConverter();

  @override
  List? fromJson(List? json) => json ?? [];

  @override
  List? toJson(List? list) => (list == null || list.isEmpty) ? null : list;
}

And here is my generated code (after running dart run build_runner build):

// my_model.g.dart

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'my_model.dart';

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

MyModel _$MyModelFromJson(Map<String, dynamic> json) => MyModel(
  intList:
      (json['intList'] as List<dynamic>?)
          ?.map((e) => (e as num).toInt())
          .toList() ??
      const [],
  intListWithJsonKey:
      (json['intListWithJsonKey'] as List<dynamic>?)
          ?.map((e) => (e as num).toInt())
          .toList() ??
      const [],
  strList:
      (json['strList'] as List<dynamic>?)
          ?.map((e) => const StringTrimConverter().fromJson(e as String))
          .toList() ??
      const [],
  str: json['str'] == null
      ? ''
      : const StringTrimConverter().fromJson(json['str'] as String),
  strOptional:
      _$JsonConverterFromJson<String, String>(
        json['strOptional'],
        const StringTrimConverter().fromJson,
      ) ??
      '',
);

Map<String, dynamic> _$MyModelToJson(MyModel instance) => <String, dynamic>{
  'intList': instance.intList,
  'strList': instance.strList.map(const StringTrimConverter().toJson).toList(),
  'str': const StringTrimConverter().toJson(instance.str),
  'strOptional': ?_$JsonConverterToJson<String, String>(
    instance.strOptional,
    const StringTrimConverter().toJson,
  ),
  'intListWithJsonKey': ?listToJson(instance.intListWithJsonKey),
};

Value? _$JsonConverterFromJson<Json, Value>(
  Object? json,
  Value? Function(Json json) fromJson,
) => json == null ? null : fromJson(json as Json);

Json? _$JsonConverterToJson<Json, Value>(
  Value? value,
  Json? Function(Value value) toJson,
) => value == null ? null : toJson(value);

The Good

  • StringTrimConverter works on a String.
  • StringTrimConverter works on a List<String>.
  • includeIfNull: false works on a String?.
  • @JsonKey(toJson: listToJson) works on an individual List field.
    • Note: This is the behavior I want with a JsonConverter.

The Problem

  • IgnoreEmptyListConverter is not used at all.
  • IgnoreEmptyNullableListConverter is not used at all.
  1. How do I do make this use one of the unused converters? (In other words, how do I do what I'm doing with @JsonKey without having to put @JsonKey on every List field?)
  2. Is there something I need to change with one of the converters to make it work?

Other Details

Here's the relevant part of my pubspec.yaml:
name: app
description: "demo"
publish_to: 'none'

version: 0.0.0+1

environment:
  sdk: ^3.10.8

dependencies:
  flutter:
    sdk: flutter

  equatable: ^2.0.8
  json_annotation: ^4.11.0
  meta: ^1.17.0
  yaml: ^3.1.2
  yaml_writer: ^2.0.0

dev_dependencies:
  flutter_test:
    sdk: flutter

  flutter_lints: ^6.0.0

  build_runner: ^2.10.5
  json_serializable: ^6.12.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions