Skip to content

System.Text.Json - Support property flattening - [JsonPropertyFlatten] #55120

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
bugproof opened this issue Jul 3, 2021 · 6 comments
Open
Labels
api-needs-work API needs work before it is approved, it is NOT ready for implementation area-System.Text.Json
Milestone

Comments

@bugproof
Copy link

bugproof commented Jul 3, 2021

I propose [JsonPropertyFlatten] attribute that can be used to flatten property. Similar to #[serde(flatten)] in Rust's Serde.

https://stackoverflow.com/questions/65323277/flatten-an-object-with-system-text-json

Imagine I have a class like this:

public class ResponseBase
{
    [JsonPropertyName("status")]
    [JsonConverter(typeof(StatusConverter))]
    public bool IsSuccessStatus { get; set; }
    
    [JsonPropertyName("error_message")] 
    public string? ErrorMessage { get; set; }
    
    [JsonPropertyName("error_code")] 
    public string? ErrorCode { get; set; }
}

If flattening was supported I could refactor it to something like this:

public class Error
{
    [JsonPropertyName("error_code")] 
    public string Code { get; set; }

	[JsonPropertyName("error_message")] 
    public string Message { get; set; }
}

public class ResponseBase
{
    [JsonPropertyName("status")]
    [JsonConverter(typeof(StatusConverter))]
    public bool IsSuccessStatus { get; set; }
    
    [JsonPropertyFlatten] 
    public Error? Error { get; set; }
}

But it would still serialize and deserialize like Error properties were part of the ResponseBase class instead of "error": {...} because I'm writing a client for third party api...

Possibly blocking? #31257

@bugproof bugproof added the api-suggestion Early API idea and discussion, it is NOT ready for implementation label Jul 3, 2021
@ghost ghost added area-System.Text.Json untriaged New issue has not been triaged by the area owner labels Jul 3, 2021
@ghost
Copy link

ghost commented Jul 3, 2021

Tagging subscribers to this area: @eiriktsarpalis, @layomia
See info in area-owners.md if you want to be subscribed.

Issue Details

I propose [JsonPropertyFlatten] attribute that can be used to flatten property. Similar to #[serde(flatten)] in Rust's Serde.

https://stackoverflow.com/questions/65323277/flatten-an-object-with-system-text-json

Imagine I have a class like this:

public class ResponseBase
{
    [JsonPropertyName("status")]
    [JsonConverter(typeof(StatusConverter))]
    public bool IsSuccessStatus { get; set; }
    
    [JsonPropertyName("error_message")] 
    public string? ErrorMessage { get; set; }
    
    [JsonPropertyName("error_code")] 
    public string? ErrorCode { get; set; }
}

If flattening was supported I could refactor it to something like this:

public class Error
{
    [JsonPropertyName("error_code")] 
    public string Code { get; set; }

	[JsonPropertyName("error_message")] 
    public string Message { get; set; }
}

public class ResponseBase
{
    [JsonPropertyName("status")]
    [JsonConverter(typeof(StatusConverter))]
    public bool IsSuccessStatus { get; set; }
    
    [JsonPropertyFlatten] 
    public Error Error { get; set; }
}

But it would still serialize and deserialize like Error properties were part of the ResponseBase class.

Author: bugproof
Assignees: -
Labels:

api-suggestion, area-System.Text.Json, untriaged

Milestone: -

@bugproof bugproof changed the title System.Text.Json - Support property flattening System.Text.Json - Support property flattening - [JsonPropertyFlatten] Jul 3, 2021
@Fydar
Copy link

Fydar commented Jul 3, 2021

This could be implemented using source generators.

A source generator would have to:

  • Add a [JsonIgnore] attribute to the member with the [JsonPropertyFlatten] attribute.
  • Add private properties that get and set the member properties on the object.

Using a source generator, the following code:

public class ResponseBase
{
    [JsonPropertyName("status")]
    [JsonConverter(typeof(StatusConverter))]
    public bool IsSuccessStatus { get; set; }
    
    [JsonPropertyFlatten] 
    public Error? Error { get; set; }
}

Could be converted to:

public class ResponseBase
{
    [JsonPropertyName("status")]
    [JsonConverter(typeof(StatusConverter))]
    public bool IsSuccessStatus { get; set; }
    
    [JsonPropertyFlatten] 
    [JsonIgnore]
    public Error? Error { get; set; }
    
    [JsonPropertyName("error_code")] 
    private string Code
    {
        get => Error.ErrorCode;
        set => Error.ErrorCode = value;
    }
    
    [JsonPropertyName("error_message")] 
    private string Message 
    {
        get => Error.Message;
        set => Error.Message = value;
    }
}

@eiriktsarpalis eiriktsarpalis added needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration and removed untriaged New issue has not been triaged by the area owner labels Jul 16, 2021
@eiriktsarpalis eiriktsarpalis added this to the 7.0.0 milestone Jul 16, 2021
@eiriktsarpalis
Copy link
Member

How would such a mechanism deal with potentially conflicting property names (same key occurring in both parent and child objects)? How does serde deal with flattened types whose serialization is a number or array?

It's unlikely we would consider such a feature for 7.0.0. My recommendation is to use a custom converter (should be easier to achieve using the new JsonNode to construct an intermediate representation). We can revisit for future releases if sufficient demand for this feature arises.

@eiriktsarpalis eiriktsarpalis modified the milestones: 7.0.0, Future Oct 15, 2021
@eiriktsarpalis eiriktsarpalis added api-needs-work API needs work before it is approved, it is NOT ready for implementation and removed needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration labels Oct 15, 2021
@ghost
Copy link

ghost commented Oct 15, 2021

This issue has been marked with the api-needs-work label. This may suggest that the proposal requires further refinement before it can be considered for API review. Please refer to our API review guidelines for a detailed description of the process.

When ready to submit an amended proposal, please ensure that the original post in this issue has been updated, following the API proposal template and examples as provided in the guidelines.

@bugproof
Copy link
Author

bugproof commented Oct 20, 2021

How would such a mechanism deal with potentially conflicting property names (same key occurring in both parent and child objects)?

The same way as it deals with this. It would just treat all the properties of the flattened property as they were part of the same object. I think it should just throw an exception.

[JsonPropertyName("name")]
public string Name { get; set; }

[JsonPropertyName("name")]
public string Name2 { get; set; }

How does serde deal with flattened types whose serialization is a number or array?

Not sure about that. But it should just support class/struct as I see no reason to flatten number or array. If you try to flatten number or array it should ignore it or throw an exception. I see no reason why would anyone do this.

@DjArt
Copy link

DjArt commented Nov 18, 2023

Not sure about that. But it should just support class/struct as I see no reason to flatten number or array. If you try to flatten number or array it should ignore it or throw an exception. I see no reason why would anyone do this.

ofc we should able to flat array or any class with indexer. Just introduce new attribute (or constructor overload for JsonPropertyName), where one of parameter will be a text representation of index value, second for original property name and third for JSON name.

And we get something like that:

public class ResponseBase
{
    [JsonPropertyName("status")]
    [JsonConverter(typeof(StatusConverter))]
    public bool IsSuccessStatus { get; set; }
    
    [JsonPropertyFlatten]
    [JsonPropertyName(nameof(Error.Status), "status")]
    [JsonPropertyName(nameof(Error.Message), "message")]
    [JsonPropertyName(nameof(Error.Lines), "0", "first_line")]
    public Error? Error { get; set; }

    [JsonPropertyFlatten]
    [JsonPropertyName("", "0", "C0")]
    [JsonPropertyName("", "1", "C1")]
    [JsonPropertyName("", "2", "C2")]
    public int[] Codes { get; set; }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-needs-work API needs work before it is approved, it is NOT ready for implementation area-System.Text.Json
Projects
None yet
Development

No branches or pull requests

4 participants