Skip to content

JSON: Allow weakly-typed mapping via Dictionary #29825

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
Tracked by #30731
roji opened this issue Dec 11, 2022 · 17 comments
Open
Tracked by #30731

JSON: Allow weakly-typed mapping via Dictionary #29825

roji opened this issue Dec 11, 2022 · 17 comments

Comments

@roji
Copy link
Member

roji commented Dec 11, 2022

#28871 tracks weakly-typed JSON mapping via JsonDocument/JsonElement, where the JSON document schema varies and so a strongly-typed model isn't appropriate.

We can also allow simply mapping arbitrary Dictionary types - this corresponds to how many people use

  • This intersects with #28688 (primitive collections in JSON).
  • Both keys and values should support any valid JSON data type (string, int...).
  • However we could also support Dictionary<string, object>, to map an entire hierarchy. See #26903 on this (currently seemed blocked by property bag detection).
  • This should be supported at the top-level, so that the JSON object as a whole is mapped as a single dictionary (see e.g. this issue). This would mean supporting OwnsOne(x => x.Json, builder => { builder.ToJson(); }) where x.Json is a Dictionary.
@roji
Copy link
Member Author

roji commented Dec 12, 2022

Duplicate of #29427

@roji roji marked this as a duplicate of #29427 Dec 12, 2022
@roji roji closed this as not planned Won't fix, can't repro, duplicate, stale Dec 12, 2022
@ajcvickers
Copy link
Contributor

Re-opening, since dictionary mapping was not covered by "primitive collections".

@vchirikov
Copy link

Link to the "issue" should be in the docs at least, was difficult to find..
Is it planned for 9.0 release?

@roji
Copy link
Member Author

roji commented Mar 20, 2024

@vchirikov the milestone generally expresses whether an issue is planned for a given release or not.

@onionhammer
Copy link

onionhammer commented Apr 18, 2024

I assume this would also meant support for ExtensionData and Dictionary<string, JsonElement> ?

@roji
Copy link
Member Author

roji commented Apr 19, 2024

@onionhammer that would be a combination of this and #28871.

@onionhammer
Copy link

onionhammer commented Apr 19, 2024

@roji putting querying aside for now, JsonElement can already be saved/serialized as an entity property, but Dictionary<string, JsonElement> cannot;

@bugproof

This comment has been minimized.

@roji

This comment has been minimized.

@bugproof

This comment has been minimized.

@roji

This comment has been minimized.

ajcvickers added a commit that referenced this issue Jul 29, 2024
Fixes #34105

This code will be consolidated with the relational UTF8 JSON code when #29825 is implemented. For now, just adding back what was already in Cosmos.
ajcvickers added a commit that referenced this issue Jul 29, 2024
Fixes #34105

This code will be consolidated with the relational UTF8 JSON code when #29825 is implemented. For now, just adding back what was already in Cosmos.
ajcvickers added a commit that referenced this issue Aug 2, 2024
Fixes #34105

This code will be consolidated with the relational UTF8 JSON code when #29825 is implemented. For now, just adding back what was already in Cosmos.
ajcvickers added a commit that referenced this issue Aug 2, 2024
* Add back support for Cosmos nested dictionaries

Fixes #34105

This code will be consolidated with the relational UTF8 JSON code when #29825 is implemented. For now, just adding back what was already in Cosmos.

* Review updates
@aleksvujic

This comment has been minimized.

@roji

This comment has been minimized.

@aleksvujic
Copy link

@roji You wrote in this comment that mappings like Dictionary<string, string> aren't supported yet and suggested using value converters instead.

I configured serialization and deserialization with HasConversion like this:

modelBuilder.Entity<Result>(entity =>
{
    entity.OwnsOne(x => x.OutputJSON, x =>
    {
        x.ToJson("output");

        x.Property(y => y.Data)
            .HasConversion(
                v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null),
                v => JsonSerializer.Deserialize<Dictionary<string, string?>>(v, (JsonSerializerOptions?)null)
            );
    });
});

However, when I perform fitDbContext.Results.ToList(), the following exception is thrown:

System.InvalidOperationException: 'Cannot get the value of a token type 'StartObject' as a string.'

I even tried to perform dummy serialization and deserialization like this, but the exception is still the same:

x.Property(y => y.Data)
    .HasConversion(
        v => "{}",
        v => new Dictionary<string, string?>()
    );

Am I using value conversion wrong?

@roji
Copy link
Member Author

roji commented Mar 27, 2025

@aleksvujic I'm a bit confused by the above - above you have an entity type Result, containing a JSON-mapped owned entity (OutputJSON), and inside that you want to have a Data property that's another JSON sub-document mapped to a dictionary? So a strongly-typed JSON document (OutputJson) containing a weakler-typed JSON document (Data)? Is that the intention here?

If so, note that the entire weakly-typed dictionary (Data) would be serialized as a single string inside the larger JSON document. So you wouldn't get { "Data": { "x" : 3 } }, but rather { "Data": "{\"x\": 3 }" } (note that quotes around Data value. I'm not sure that's what you want.

@aleksvujic
Copy link

aleksvujic commented Mar 27, 2025

@roji I think you summarized it correctly. JSON in the output column in PostgreSQL table looks like this:

{
  "data": {
    "john": "doe",
    "hello": "world"
  }
}

The data field in the JSON is static and is always there, its content is dynamic (dictionary - (string, string) pairs).

That's why I wanted to model data in C# like this with Dictionary<string, string>:

// scaffolded DB model
[Table("results", Schema = "fit3")]
public partial class Result
{
    [Key]
    [Column("id")]
    public int Id { get; set; }

    [Column("output", TypeName = "jsonb")]
    public string? Output { get; set; }
}

// custom class with additional strongly-typed OutputJSON property
public partial class Result
{
    public Output? OutputJSON { get; set; }
}

// content of the "data" JSON field (dictionary)
public class Output
{
    [JsonPropertyName("data")]
    public Dictionary<string, string?>? Data { get; set; }
}

If I understand correctly, this can't work - this exact problem is tracked by this issue. Correct?

If I format the nested JSON document in the PostgreSQL table as string (shown below), then EF successfuly maps it to C# Dictionary<string, string> when I perform fitDbContext.Results.ToList().

{"data": "{ \"john\": \"doe\",  \"hello\": \"world\" }"}

@roji
Copy link
Member Author

roji commented Mar 27, 2025

If I understand correctly, this can't work - this exact problem is tracked by this issue. Correct?

No. EF already supports mapping anything via value converters - whether it be a dictionary or something else. This issue tracks allowing mapping a Dictionary to JSON without value converters, which would mean that EF manages the conversion and knows what's going on; that would notably mean that you can e.g. use dictionary lookups inside queries, which is something that you can't do when value-converting a value.

Now, there may be an EF bug specifically when nesting your value-converted dictionary within another JSON owned entity; but that wouldn't be related to this issue.

Can you please open a new issue and include a minimal repro for the problem?

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

No branches or pull requests

8 participants