Search code examples
flutterdarthttpjson-serializable

JsonSerializable. Can't match Model to json schema


The following json is the response body of a request at Google Place Details Api.

{
    "html_attributions": [],
    "result": {
        "address_components": [
            {
                "long_name": "18",
                "short_name": "18",
                "types": [
                    "street_number"
                ]
            },
            {
                "long_name": "Doiranis",
                "short_name": "Doiranis",
                "types": [
                    "route"
                ]
            },
            {
                "long_name": "Thessaloniki",
                "short_name": "Thessaloniki",
                "types": [
                    "locality",
                    "political"
                ]
            },
            {
                "long_name": "Thessaloniki",
                "short_name": "Thessaloniki",
                "types": [
                    "administrative_area_level_3",
                    "political"
                ]
            },
            {
                "long_name": "Greece",
                "short_name": "GR",
                "types": [
                    "country",
                    "political"
                ]
            },
            {
                "long_name": "546 39",
                "short_name": "546 39",
                "types": [
                    "postal_code"
                ]
            }
        ]
    },
    "status": "OK"
}

The following is my Place model class

import 'package:json_annotation/json_annotation.dart';

part 'place_model.g.dart';

@JsonSerializable(fieldRename: FieldRename.snake)
class Place {
  final String streetName;
  final String streetNumber;
  final String city;
  final String zipCode;

  Place({
    required this.streetName,
    required this.streetNumber,
    required this.city,
    required this.zipCode,
  });

  factory Place.fromJson(Map<String, dynamic> json) => _$PlaceFromJson(json);
}

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'place_model.dart';

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

Place _$PlaceFromJson(Map<String, dynamic> json) => Place(
      streetName: json['street_name'] as String,
      streetNumber: json['street_number'] as String,
      city: json['city'] as String,
      zipCode: json['zip_code'] as String,
    );

Map<String, dynamic> _$PlaceToJson(Place instance) => <String, dynamic>{
      'street_name': instance.streetName,
      'street_number': instance.streetNumber,
      'city': instance.city,
      'zip_code': instance.zipCode,
    };

I need the short_name values in address_components list for my Place model, but currently my model doesn't match the json Schema. I'm an inexperienced developer and this might be simple. But could you suggest me a way to properly generate place_model.g.dart?


Solution

  • The response does not match your target class, hence you'll have to convert it first.

    Create models for the response:

    @JsonSerializable()
    class Response {
      final Result result;
      final String status;
    
      const Response({required this.result, required this.status});
    
      factory Response.fromJson(Map<String, dynamic> json) =>
          _$ResponseFromJson(json);
    }
    
    @JsonSerializable(fieldRename: FieldRename.snake)
    class Result {
      final List<AddressComponents> addressComponents;
    
      const Result({required this.addressComponents});
    
      factory Result.fromJson(Map<String, dynamic> json) => _$ResultFromJson(json);
    }
    
    @JsonSerializable(fieldRename: FieldRename.snake)
    class AddressComponents {
      final String longName;
      final String shortName;
      final List<String> types;
    
      const AddressComponents({
        required this.longName,
        required this.shortName,
        required this.types,
      });
    
      factory AddressComponents.fromJson(Map<String, dynamic> json) =>
          _$AddressComponentsFromJson(json);
    }
    

    Now you may add a factory constructor to the Place model that will build a Place model from the Response:

    class Place {
      const Place({
        this.streetName,
        this.streetNumber,
        this.city,
        this.zipCode,
      });
    
      final String? streetName;
      final String? streetNumber;
      final String? city;
      final String? zipCode;
    
      factory Place.fromResponse(Response response) {
        final streetNumberComponent = response.result.addressComponents
            .where((c) => c.types.contains('street_number'));
        // .. here the rest of Place fields
    
        return Place(
          streetNumber: streetNumberComponent.isNotEmpty
              ? streetNumberComponent.first.shortName
              : null,
          // .. here the rest of Place fields
        );
      }
    }