google / json_serializable.dart

Generates utilities to aid in serializing to/from JSON.
https://pub.dev/packages/json_serializable
BSD 3-Clause "New" or "Revised" License
1.55k stars 396 forks source link

[Feature Request] Add `flatten` option to `JsonKey` #853

Closed yousinix closed 3 years ago

yousinix commented 3 years ago

With the following class:

@JsonSerializable()
class BasePageBody<TFilter> {
  final int page;
  final TFilter filter;

  BasePageBody(this.page, this.filter);

  // ...
}

The following JSON is generated:

{
  "page": 0,
  "filter": { 
     "someKey": ""
    }
}

But what if we need the resultant JSON flattened like this?

{
  "page": 0,
  "someKey": ""
}

Proposed approach:

@JsonSerializable()
class BasePageBody<TFilter> {
  final int page;

  @JsonKey(flatten: true)
  final TFilter filter;

  BasePageBody(this.page, this.filter);

  // ...
}
k-paxian commented 3 years ago

How is this thing should behave the other way around, when Json => becomes an BasePageBody<TFilter>

yousinix commented 3 years ago

Concerning the fromJson part, we just get a map containing all the fields in filter, and call filter's fromJson using it.

I removed generic parameters for simpicity in the following example

@JsonSerializable()
class Filter {
  final String someKey;
  // ...
}

@JsonSerializable()
class BasePageBody {
  final int page;

  @JsonKey(flatten: true)
  final Filter filter;  

  // ...
}

// GENERATED CODE 
BasePageBody _$BasePageBodyFromJson(Map<String, dynamic> json) {
  return BasePageBody(
    page: json['page'] as int,
    filter: Filter.fromJson({
      'someKey': json['someKey'] as String,
    }),
  );
}

And for the conflict part, let's say we have this class:

@JsonSerializable()
class SomeClass {
  @JsonKey(name: 'pageKey')
  final int pageNumber;
  final int pageKey;
  // ...
}

This class has a conflict too, it throws and error as follows when trying to generate:

More than one field has the JSON key for name "pageKey".
***:00:00
   ╷
00 │   final int pageKey;
   │             ^^^^^^^
   ╵

This could be the way we handle a conflict in the case of flattening, nothing different.

kevmoo commented 3 years ago

Likely not going to add this here. Consider using https://pub.dev/packages/dart_json_mapper

definev commented 11 months ago

https://github.com/helgoboss/json_serializable.dart/commit/df0c13811d5775080b0ea60d8094331b223f57d9 Can we merge this PR with json_serializable? I think deserialized map works fine in most cases 👍

helgoboss commented 6 months ago

@definev Oh sorry, somehow I missed your comment. I'm not sure if it's a good idea. It works in my case but it's not something I tested extensively for other scenarios. On the other hand, maybe better than nothing ...

l-7-l commented 6 months ago

I don't understand why this PR wasn't accepted. This function is quite useful. Especially the use cases of inheriting/composing classes, such as:

#[derive(Debug, Serialize)]
pub struct BaseRegion {
    pub code: i64,
    pub name: String,
    pub letter: Letter,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct Province {
    #[serde(flatten)]
    // #[sqlx(flatten)]
    pub base: BaseRegion,
    #[serde(default = "default_country")]
    pub country: i16,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all(deserialize = "camelCase"))]
pub struct City {
    #[serde(flatten)]
    pub base: BaseRegion,
    #[serde(rename(serialize = "province"))]
    pub province_code: i64,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all(deserialize = "camelCase"))]
pub struct District {
    #[serde(flatten)]
    pub base: BaseRegion,
    pub city_code: i64,
    pub province_code: i64,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all(deserialize = "camelCase"))]
pub struct Subdistrict {
    #[serde(flatten)]
    pub base: BaseRegion,

    #[serde(rename(serialize = "province"))]
    pub province_code: i64,
    #[serde(rename(serialize = "city"))]
    pub city_code: i64,
    #[serde(rename(serialize = "district", deserialize = "areaCode"))]
    pub district_code: i64,
}

In dart, if there is no flatten, the fields of BaseRegion need to be repeated over and over again.

helgoboss commented 6 months ago

@l-7-l Is there a PR? If you are referring to https://github.com/helgoboss/json_serializable.dart/commit/df0c13811d5775080b0ea60d8094331b223f57d9, this is just a commit that I made in my fork while referring to this issue. I never opened a PR.

l-7-l commented 6 months ago

@helgoboss Ah ha, I made a mistake~ thank you sir. I understand that the maintainers are busy and have done a great job, but I don't understand why Likely not going to add this here.

there is a lot of boilerplate code here


@freezed
class Province with _$Province {
  const factory Province({
    required int code,
    required String name,
    required String letter,
    required int country,
  }) = _Province;

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

@freezed
class City with _$City {
  const factory City({
    required int code,
    required String name,
    required String letter,
    required int province,
  }) = _City;

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

@freezed
class District with _$District {
  const factory District({
    required int code,
    required String name,
    required String letter,
    required int province,
    required int city,
  }) = _District;

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

@freezed
class Subdistrict with _$Subdistrict {
  const factory Subdistrict({
    required int code,
    required String name,
    required String letter,
    required int province,
    required int city,
    required int district,
  }) = _Subdistrict;

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