Home > Blockchain >  How to deserialize generic JSON with List type in Dart
How to deserialize generic JSON with List type in Dart

Time:05-16

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?

CodePudding user response:

There is a website which automatically generates all needed code from json. Here is example:

// To parse this JSON data, do
//
//     final entry = entryFromJson(jsonString);

import 'dart:convert';

Entry entryFromJson(String str) => Entry.fromJson(json.decode(str));

String entryToJson(Entry data) => json.encode(data.toJson());

class Entry {
    Entry({
        this.data,
    });

    List<Datum> data;

    factory Entry.fromJson(Map<String, dynamic> json) => Entry(
        data: List<Datum>.from(json["data"].map((x) => Datum.fromJson(x))),
    );

    Map<String, dynamic> toJson() => {
        "data": List<dynamic>.from(data.map((x) => x.toJson())),
    };
}

class Datum {
    Datum({
        this.id,
        this.title,
        this.createdDate,
        this.content,
        this.author,
        this.category,
    });

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

    factory Datum.fromJson(Map<String, dynamic> json) => Datum(
        id: json["id"],
        title: json["title"],
        createdDate: DateTime.parse(json["createdDate"]),
        content: json["content"],
        author: json["author"],
        category: json["category"],
    );

    Map<String, dynamic> toJson() => {
        "id": id,
        "title": title,
        "createdDate": createdDate.toIso8601String(),
        "content": content,
        "author": author,
        "category": category,
    };
}

CodePudding user response:

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

  • Related