Search code examples
jsonflutterdartdeserialization

How to deserialize generic JSON with List type in Dart


I want to deserialize some JSON data that contains a list of article information

{
    "data": [
        {
            "id": 1,
            "title": "First article",
            "createdDate": "2022-03-20T11:46:00",
            "content": "Markdown content",
            "author": 1,
            "category": 1
        },
        {
            "id": 2,
            "title": "Second article",
            "createdDate": "2022-03-20T11:46:00",
            "content": "Markdown content",
            "author": 1,
            "category": 1
        }
    ]
}

No matter what the request is, the top level will have a key called data

So, I created a generic class called Entry

import 'package:json_annotation/json_annotation.dart';

part 'Entry.g.dart';

@JsonSerializable(genericArgumentFactories: true)

class Entry<TData> {
  Entry(this.data);

  TData data;

  factory Entry.fromJson(Map<String, dynamic> json,TData Function(dynamic json) fromJsonTData) => _$EntryFromJson(json,fromJsonTData);
  Map<String, dynamic> toJson(Object? Function(TData value) toJsonTData) => _$EntryToJson(this,toJsonTData);
}

And for an article, I created a class call NovelData

import 'dart:convert';
import 'dart:core';
import 'package:json_annotation/json_annotation.dart';

import 'Entry.dart';

part 'NovelData.g.dart';

@JsonSerializable(genericArgumentFactories: true)
class NovelData {
  NovelData(this.id, this.title, this.createdDate, this.content, this.author, this.category);

  int id;
  String title;
  String createdDate;
  String content;
  int author;
  int category;

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

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

Now, if I want to use the type like Entry<List<Novel>>> to deserialize the above JSON data, what should I do?


Solution

  • You can access them through the full path to the data.
    Full path to your data: Map => key data => Array => Array index => Map
    {}.data.[].0.{}
    It only takes one class.

    import 'package:fast_json/fast_json_selector.dart' as parser;
    
    void main() async {
      final path = '{}.data.[].0.{}';
      final pathLevel = path.split('.').length;
      final items = <Novel>[];
      void select(parser.JsonSelectorEvent event) {
        if (event.levels.length == pathLevel) {
          if (event.levels.join('.') == path) {
            final item = Novel.fromJson(event.lastValue as Map);
            items.add(item);
            event.lastValue = null;
          }
        }
      }
    
      parser.parse(_json, select: select);
      print(items.join('\n'));
    }
    
    final _json = '''
    {
        "data": [
            {
                "id": 1,
                "title": "First article",
                "createdDate": "2022-03-20T11:46:00",
                "content": "Markdown content",
                "author": 1,
                "category": 1
            },
            {
                "id": 2,
                "title": "Second article",
                "createdDate": "2022-03-20T11:46:00",
                "content": "Markdown content",
                "author": 1,
                "category": 1
            }
        ]
    }''';
    
    class Novel {
      final int id;
      final String title;
    
      Novel({required this.id, required this.title});
    
      @override
      String toString() {
        return title;
      }
    
      static Novel fromJson(Map json) {
        return Novel(
          id: json['id'] as int,
          title: json['title'] as String,
        );
      }
    }
    
    

    Output:

    First article
    Second article
    

    You can get the data before adding it to the list. The result is no different. Just a different path to the data.

    void main() async {
      final path = '{}.data.[].0';
      final pathLevel = path.split('.').length;
      final items = <Novel>[];
      void select(parser.JsonSelectorEvent event) {
        if (event.levels.length == pathLevel) {
          if (event.levels.join('.') == path) {
            final item = Novel.fromJson(event.lastValue as Map);
            items.add(item);
            event.lastValue = null;
          }
        }
      }
    
      parser.parse(_json, select: select);
      print(items.join('\n'));
    }
    

    This event follows the object creation event (at a lower event level):
    JsonHandlerEvent.endObject => JsonHandlerEvent.element

    You can get the data after adding it to the list. But it won't be as efficient.

    void main() async {
      final path = '{}.data.[]';
      final pathLevel = path.split('.').length;
      final items = <Novel>[];
      void select(parser.JsonSelectorEvent event) {
        if (event.levels.length == pathLevel) {
          if (event.levels.join('.') == path) {
            final list = event.lastValue as List;
            items.addAll(list.map((e) => Novel.fromJson(e as Map)));
            list.clear();
          }
        }
      }
    
      parser.parse(_json, select: select);
      print(items.join('\n'));
    }
    

    JsonHandlerEvent.endObject => JsonHandlerEvent.element => JsonHandlerEvent.endArray

    Or even from property data. Very inefficient because all data is stored in memory.

    void main() async {
      final path = '{}.data';
      final pathLevel = path.split('.').length;
      final items = <Novel>[];
      void select(parser.JsonSelectorEvent event) {
        if (event.levels.length == pathLevel) {
          if (event.levels.join('.') == path) {
            final list = event.lastValue as List;
            items.addAll(list.map((e) => Novel.fromJson(e as Map)));
            event.lastValue = null;
          }
        }
      }
    
      parser.parse(_json, select: select);
      print(items.join('\n'));
    }
    

    JsonHandlerEvent.endObject => JsonHandlerEvent.element => JsonHandlerEvent.endArray => JsonHandlerEvent.endKey

    I won't even write about the last level. There is no point in such an inefficient way. However, and in the previous one, too.

    JsonHandlerEvent.endObject => JsonHandlerEvent.element => JsonHandlerEvent.endArray => JsonHandlerEvent.endKey => JsonHandlerEvent.endObject