Search code examples
jsondartparse-platformback4app

How to decode from json in this situation?


I am saving an entire class as a json object on my Parse Back4App server. It was a pain getting the object to parse to json because I needed to turn many properties into string in both nested class Book and main class DatabaseSyncItem. Now I have no idea how to turn this json object back into a DatabaseSyncObject. I assume the same would go for getting the nested json book objects back to object model, that is that it wont work this way.

I'm getting errors and have no success. If I try to access a specific index from my list of json objects in main I can get somewhere but this is very messy. What would you do in this situation? Help me out here, my first time working with json.

Tried applying these posts but no workey:

import 'dart:convert';
import 'dart:io';
import 'package:hive/hive.dart';
import 'package:parse_server_sdk/parse_server_sdk.dart';
import 'package:service_database_sync/data/books_hardcoded.dart';
import 'package:service_database_sync/models/book_model.dart';
import 'package:service_database_sync/models/database_sync_model.dart';
import 'package:service_database_sync/services/demo_services.dart';
import 'package:service_database_sync/services/server_database_services.dart';

Future<void> main(List<String> arguments) async {
  Hive.init('hive_database');
  Hive.registerAdapter(BookAdapter());
  await Parse().initialize(
    ServerDatabaseServices().keyApplicationId,
    ServerDatabaseServices().keyParseServerUrl,
    clientKey: ServerDatabaseServices().keyClientKey,
    debug: true,
  );

  final test = updateLocalDatabase();
  Future<List> getList() {
    return Future.value(test);
  }

  var list = await getList();
  var jsonObject = list[2]['DatabaseSyncItem'];
  // var backToObject = jsonDecode(jsonObject); // error: type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'String'
  var backToObject = DatabaseSyncItem.fromJson(jsonObject); // error: type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'Book'
  print(backToObject);
}

// This method will get server database
  Future<List<ParseObject>> updateLocalDatabase() async {
    final events = QueryBuilder<ParseObject>(ParseObject('Event'));
    final apiResponse = await events.query();
    if (apiResponse.success && apiResponse.result != null) {
      return apiResponse.results;
    } else {
      return [];
    }
  }

part 'database_sync_model.g.dart';

@HiveType(typeId: 1)
class DatabaseSyncItem {
  @HiveField(0)
  Book previousBookValue;

  @HiveField(1)
  Book updatedBookValue;

  @HiveField(2)
  DateTime dateAdded;

  @HiveField(3)
  DateTime lastModified;

  @HiveField(4)
  DatabaseAction entryAction;

  DatabaseSyncItem({
    this.previousBookValue,
    this.updatedBookValue,
    this.dateAdded,
    this.lastModified,
    this.entryAction,
  });

  @override
  String toString() {
  return '''
  previousValue: $previousBookValue
  updatedValue: $updatedBookValue
  dateAdded: $dateAdded
  lastModified: $lastModified
  entryAction: $entryAction
  ''';
   }

  // Turn json back into data model
  DatabaseSyncItem.fromJson(Map<String, dynamic> json)
      : previousBookValue = json['previousBookValue'],
        updatedBookValue = json['updatedBookValue'],
        dateAdded = json['dateAdded'],
        lastModified = json['lastModified'],
        entryAction = json['entryAction'];

  // Turn data model into json
  Map<String, dynamic> toJson() => {
        'previousBookValue': previousBookValue,
        'updatedBookValue': updatedBookValue,
        'dateAdded': dateAdded,
        'lastModified': lastModified,
        'entryAction': entryAction.toString(),
      };
}

enum DatabaseAction {
  create,
  update,
  delete,
}

import 'package:hive/hive.dart';
part 'book_model.g.dart';

@HiveType(typeId: 0)
class Book {
  @HiveField(0)
  String title;

  @HiveField(1)
  String author;

  @HiveField(2)
  DateTime publishingDate;

  @HiveField(3)
  DateTime dateAdded;

  @HiveField(4)
  DateTime lastModified;

  Book({
    this.title,
    this.author,
    this.publishingDate,
    this.dateAdded,
    this.lastModified,
  });

  @override
  String toString() {
  return '''
  title: $title
  author: $author
  publishingDate: $publishingDate
  dateAdded: $dateAdded
  lastModified $lastModified
  ''';
   }

  Book.fromJson(Map<String, dynamic> json)
      : title = json['title'],
        author = json['author'],
        publishingDate = json['publishingDate'],
        dateAdded = json['dateAdded'],
        lastModified = json['lastModified'];

  Map<String, dynamic> toJson() => {
        'title': title,
        'author': author,
        'publishingDate': publishingDate.toIso8601String(),
        'dateAdded': dateAdded.toIso8601String(),
        'lastModified': lastModified.toIso8601String()
      };
}


Solution

  • Seems like this answer is way more complex than I initially anticipated. Need to use json serializable package and json annotation package.

    The packages are necessary and you can see why when you look at the generated code for handling types, null as well as a nested class. In case anyone else runs into this.

    import 'package:hive/hive.dart';
    import 'package:json_annotation/json_annotation.dart';
    part 'book_model.g.dart';
    
    @JsonSerializable(includeIfNull: true)
    @HiveType(typeId: 0)
    class Book {
      @HiveField(0)
      String title;
    
      @HiveField(1)
      String author;
    
      @HiveField(2)
      DateTime publishingDate;
    
      @HiveField(3)
      DateTime dateAdded;
    
      @HiveField(4)
      DateTime lastModified;
    
      Book({
        required this.title,
        required this.author,
        required this.publishingDate,
        required this.dateAdded,
        required this.lastModified,
      });
    
      factory Book.fromJson(Map<String, dynamic> json) => _$BookFromJson(json);
      Map<String, dynamic> toJson() => _$BookToJson(this);
    
      @override
      String toString() {
        return '''
      title: $title
      author: $author
      publishingDate: $publishingDate
      dateAdded: $dateAdded
      lastModified $lastModified
      ''';
      }
    }
    
    // GENERATED CODE - DO NOT MODIFY BY HAND
    
    part of 'book_model.dart';
    
    // **************************************************************************
    // TypeAdapterGenerator
    // **************************************************************************
    
    class BookAdapter extends TypeAdapter<Book> {
      @override
      final int typeId = 0;
    
      @override
      Book read(BinaryReader reader) {
        final numOfFields = reader.readByte();
        final fields = <int, dynamic>{
          for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
        };
        return Book(
          title: fields[0] as String,
          author: fields[1] as String,
          publishingDate: fields[2] as DateTime,
          dateAdded: fields[3] as DateTime,
          lastModified: fields[4] as DateTime,
        );
      }
    
      @override
      void write(BinaryWriter writer, Book obj) {
        writer
          ..writeByte(5)
          ..writeByte(0)
          ..write(obj.title)
          ..writeByte(1)
          ..write(obj.author)
          ..writeByte(2)
          ..write(obj.publishingDate)
          ..writeByte(3)
          ..write(obj.dateAdded)
          ..writeByte(4)
          ..write(obj.lastModified);
      }
    
      @override
      int get hashCode => typeId.hashCode;
    
      @override
      bool operator ==(Object other) =>
          identical(this, other) ||
          other is BookAdapter &&
              runtimeType == other.runtimeType &&
              typeId == other.typeId;
    }
    
    // **************************************************************************
    // JsonSerializableGenerator
    // **************************************************************************
    
    Book _$BookFromJson(Map<String, dynamic> json) {
      return Book(
        title: json['title'] as String,
        author: json['author'] as String,
        publishingDate: DateTime.parse(json['publishingDate'] as String),
        dateAdded: DateTime.parse(json['dateAdded'] as String),
        lastModified: DateTime.parse(json['lastModified'] as String),
      );
    }
    
    Map<String, dynamic> _$BookToJson(Book instance) => <String, dynamic>{
          'title': instance.title,
          'author': instance.author,
          'publishingDate': instance.publishingDate.toIso8601String(),
          'dateAdded': instance.dateAdded.toIso8601String(),
          'lastModified': instance.lastModified.toIso8601String(),
        };
    
    import 'package:json_annotation/json_annotation.dart';
    import 'book_model.dart';
    import 'package:hive/hive.dart';
    part 'database_sync_model.g.dart';
    
    @JsonSerializable(includeIfNull: true)
    @HiveType(typeId: 1)
    class DatabaseSyncItem {
      @HiveField(0)
      Book? previousBookValue;
    
      @HiveField(1)
      Book? updatedBookValue;
    
      @HiveField(2)
      DateTime dateAdded;
    
      @HiveField(3)
      DateTime lastModified;
    
      @HiveField(4)
      DatabaseAction entryAction;
    
      DatabaseSyncItem({
        this.previousBookValue,
        this.updatedBookValue,
        required this.dateAdded,
        required this.lastModified,
        required this.entryAction,
      });
    
      factory DatabaseSyncItem.fromJson(Map<String, dynamic> json) => _$DatabaseSyncItemFromJson(json);
      Map<String, dynamic> toJson() => _$DatabaseSyncItemToJson(this);
    
      @override
      String toString() {
      return '''
      previousValue: $previousBookValue
      updatedValue: $updatedBookValue
      dateAdded: $dateAdded
      lastModified: $lastModified
      entryAction: $entryAction
      ''';
       }
    }
    
    enum DatabaseAction {
      create,
      update,
      delete,
    }
    
    // GENERATED CODE - DO NOT MODIFY BY HAND
    
    part of 'database_sync_model.dart';
    
    // **************************************************************************
    // TypeAdapterGenerator
    // **************************************************************************
    
    class DatabaseSyncItemAdapter extends TypeAdapter<DatabaseSyncItem> {
      @override
      final int typeId = 1;
    
      @override
      DatabaseSyncItem read(BinaryReader reader) {
        final numOfFields = reader.readByte();
        final fields = <int, dynamic>{
          for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
        };
        return DatabaseSyncItem(
          previousBookValue: fields[0] as Book?,
          updatedBookValue: fields[1] as Book?,
          dateAdded: fields[2] as DateTime,
          lastModified: fields[3] as DateTime,
          entryAction: fields[4] as DatabaseAction,
        );
      }
    
      @override
      void write(BinaryWriter writer, DatabaseSyncItem obj) {
        writer
          ..writeByte(5)
          ..writeByte(0)
          ..write(obj.previousBookValue)
          ..writeByte(1)
          ..write(obj.updatedBookValue)
          ..writeByte(2)
          ..write(obj.dateAdded)
          ..writeByte(3)
          ..write(obj.lastModified)
          ..writeByte(4)
          ..write(obj.entryAction);
      }
    
      @override
      int get hashCode => typeId.hashCode;
    
      @override
      bool operator ==(Object other) =>
          identical(this, other) ||
          other is DatabaseSyncItemAdapter &&
              runtimeType == other.runtimeType &&
              typeId == other.typeId;
    }
    
    // **************************************************************************
    // JsonSerializableGenerator
    // **************************************************************************
    
    DatabaseSyncItem _$DatabaseSyncItemFromJson(Map<String, dynamic> json) {
      return DatabaseSyncItem(
        previousBookValue: json['previousBookValue'] == null
            ? null
            : Book.fromJson(json['previousBookValue'] as Map<String, dynamic>),
        updatedBookValue: json['updatedBookValue'] == null
            ? null
            : Book.fromJson(json['updatedBookValue'] as Map<String, dynamic>),
        dateAdded: DateTime.parse(json['dateAdded'] as String),
        lastModified: DateTime.parse(json['lastModified'] as String),
        entryAction: _$enumDecode(_$DatabaseActionEnumMap, json['entryAction']),
      );
    }
    
    Map<String, dynamic> _$DatabaseSyncItemToJson(DatabaseSyncItem instance) =>
        <String, dynamic>{
          'previousBookValue': instance.previousBookValue,
          'updatedBookValue': instance.updatedBookValue,
          'dateAdded': instance.dateAdded.toIso8601String(),
          'lastModified': instance.lastModified.toIso8601String(),
          'entryAction': _$DatabaseActionEnumMap[instance.entryAction],
        };
    
    K _$enumDecode<K, V>(
      Map<K, V> enumValues,
      Object? source, {
      K? unknownValue,
    }) {
      if (source == null) {
        throw ArgumentError(
          'A value must be provided. Supported values: '
          '${enumValues.values.join(', ')}',
        );
      }
    
      return enumValues.entries.singleWhere(
        (e) => e.value == source,
        orElse: () {
          if (unknownValue == null) {
            throw ArgumentError(
              '`$source` is not one of the supported values: '
              '${enumValues.values.join(', ')}',
            );
          }
          return MapEntry(unknownValue, enumValues.values.first);
        },
      ).key;
    }
    
    const _$DatabaseActionEnumMap = {
      DatabaseAction.create: 'create',
      DatabaseAction.update: 'update',
      DatabaseAction.delete: 'delete',
    };