Skip to content

Add aliases to JsonValue to enum value to be decoded from different JSON values#1459

Open
mateusfccp wants to merge 1 commit into
google:masterfrom
mateusfccp:1380
Open

Add aliases to JsonValue to enum value to be decoded from different JSON values#1459
mateusfccp wants to merge 1 commit into
google:masterfrom
mateusfccp:1380

Conversation

@mateusfccp

@mateusfccp mateusfccp commented Nov 26, 2024

Copy link
Copy Markdown

Solves #1380.

Adds a parameter to JsonValue called aliases, which accepts a list of values that can be alternatively used to decode a JSON.

Example:

enum StatusCode {
  @JsonValue(200, aliases: [201, 202])
  success,
  @JsonValue(301)
  movedPermanently,
  @JsonValue(302)
  found,
  @JsonValue(500)
  internalServerError,
}

With the code above, all of 200, 201 and 202 would be decoded as StatusCode.success.

When calling toJson, however, the value parameter is used. In this case, 200.

Notes

  • The $enumDecode and $enumDecodeNullable were not changed because they are public and there are packages that depend on them. Instead, $enumDecodeWithDecodeMap and $enumDecodeNullableWithDecodeMap were introduced. We may want to deprecate the former at some point and remove them in a major version.

Comment thread json_annotation/lib/src/json_value.dart Outdated
@kevmoo

kevmoo commented Dec 2, 2024

Copy link
Copy Markdown
Collaborator

Ideally we'd have a couple of tests to validate the failure cases, but I get that those can be tricky to add.

@mateusfccp

Copy link
Copy Markdown
Author

@kevmoo What would be the failure cases for this PR? Can you give an example?

@kevmoo

kevmoo commented Dec 2, 2024

Copy link
Copy Markdown
Collaborator

@kevmoo What would be the failure cases for this PR? Can you give an example?

Unsupported types in the set is the big one.
null isn't supported, right?
Those types of things.

@mateusfccp

mateusfccp commented Dec 2, 2024

Copy link
Copy Markdown
Author

@kevmoo What would be the failure cases for this PR? Can you give an example?

Unsupported types in the set is the big one. null isn't supported, right? Those types of things.

Ah, yes, the idea is to mimic value, so until we have proper union types in Dart, we use Object? but should only accept String and int.

I am going to work on this test.

@kevmoo

kevmoo commented Dec 17, 2024

Copy link
Copy Markdown
Collaborator

You'll need to rebase this fix.

@mateusfccp

Copy link
Copy Markdown
Author

You'll need to rebase this fix.

I rebased to master and included the requested test.

In my local machine, a test was failing, but I didn't change anything related to it (the *ToJson generator).

image

@mateusfccp

Copy link
Copy Markdown
Author

Mmm, I had made a regular merge, but GitHub is saying to me that "this branch must not contain merge commits", so I rewrote the commits using a rebase instead, but it is still giving me this message.

@mateusfccp

mateusfccp commented Dec 18, 2024

Copy link
Copy Markdown
Author

Well, the test that did not pass locally passed here, so we are good with it.

@mateusfccp

mateusfccp commented May 23, 2025

Copy link
Copy Markdown
Author

@kevmoo Is there something preventing the code review from proceeding?

@kevmoo kevmoo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the repeated map EVERYWHERE for one-off feature.

Could you noodle on that a bit?

Comment thread json_serializable/lib/src/enum_utils.dart Outdated
};

// ignore: unused_element
const _$GreekEnumDecodeMap = {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we're changing the generated code and creating a NEW map, even if the dev doesn't use the new feature?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kevmoo Yes, basically.

The old map is left as is. However, in the current code, the map is used both for encoding and decoding. Decoding is done by linearly checking if an entry has the expected value for that key. It works similar to a Bimap.

By using two maps, we have O(1) access to the expected decoded value. But this is only a consequence of this approach, and I don't even think it will make any difference in practice unless someone decodes an outstanding quantity of Json values.

The main point is that if we introduce aliases, we have an asymmetric relationship between the two sets (the sets of encoded and decoded values). We want encoding to have a single resulting value (it is the one defined by the first parameter of the JsonValue constructor), but it can be decoded by more than one value (which is this same value + the ones declared in the alias parameter).

Because of this, we can't work with a single map anymore unless:

  • We revert the map and key values, so we avoid duplicated keys.
  • We guarantee that the first value is always the one that is expected when encoding.

Another approach could be to use two maps, the one we already have and an optional map aliases that will only be generated if aliases are used. Then, in the $enumDecodeWithDecodeMap function, we accept an optional aliases map as parameter, which, if passed, will first check for the entries like it is doing today and, if not found, it will try to find it in the passed aliases map (if any), falling back to a runtime error as it is today.

This could work, but I don't think it is a better approach than what I chose to use on this PR. I just splits the map into two, which guarantees O(1) access and a clear distinction between encoding/decoding expectations.

Maybe you have a third idea about how to tackle this issue?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Ps. the $enumDecode function is not even used anymore, I just didn't remove it entirely because I don't know if it would constitute a breaking change. Supposedly not, because we don't expose it in the public API, but I wanted to check with you before. If we are good, $enumDecodeWithDecodeMap can be renamed to $enumDecode, as it replaces its purpose completely with this approach)

@kevmoo

kevmoo commented Jun 4, 2025

Copy link
Copy Markdown
Collaborator

FYI: I've moved this package to Dart 3.8. You'll need to rebase...

@mateusfccp

Copy link
Copy Markdown
Author

@kevmoo I've rebased the code to the master branch.

Let me know what you think about the discussion on default_value.g.dart so we can progress.

@robpot95

robpot95 commented Feb 3, 2026

Copy link
Copy Markdown

Any news? I stumble upon this feature is lacking

@mateusfccp

Copy link
Copy Markdown
Author

Any news? I stumble upon this feature is lacking

It depends on the reviewers. The code is a bit outdated, but I can rebase it, it's just that there are pendencies that were not resolved yet. I answered to some comments asking about the best (or intended) approach and Kevin still didn't have time to answer, so let's wait.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants