Skip to content
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

Please extend the object-initialization syntax so that json literals could literally initialize json data #6949

Closed
gafter opened this issue Nov 21, 2015 · 32 comments
Labels
4 - In Review A fix for the issue is submitted for review. Area-Language Design

Comments

@gafter
Copy link
Member

gafter commented Nov 21, 2015

Please extend the syntax for object initializers so that json literals would be valid, and would cause the construction of some representation isomorphic to the json data.

It would be nice if there were some way to indicate which json data structure should be constructed, but obviously that cannot be within the data, as it would no longer be json.

@gafter gafter added Area-Language Design 4 - In Review A fix for the issue is submitted for review. labels Nov 21, 2015
@gafter
Copy link
Member Author

gafter commented Nov 21, 2015

See also #6673, which we are not likely to do because it maps an identifier to a string rather than evaluating it as an expression.

@gafter
Copy link
Member Author

gafter commented Nov 21, 2015

@AnthonyDGreen This is mostly a placeholder. You're welcome to fill it in with more detail.

@HaloFour
Copy link

@gafter You mention "object initializers" specifically, would this apply to any object or is this more specific to dictionaries?

public class Person {
    public string Name { get; set; }
    public int Age { get; set; }
}

Person person = new Person() {
    "Name" : "Bill Gates",
    "Age" : "60"
};

@gafter
Copy link
Member Author

gafter commented Nov 23, 2015

@HaloFour The intent is that this would use a pattern of construction methods common to types such as Dictionary and JObject.

@HaloFour
Copy link

@gafter Interesting, so a type would still need to be specified, or could there be a default type for the literal?

var dict = new Dictionary<string, string>() { "Name" : "Bill Gates", "Age" : "60" };
var json = new JObject() { "Name" : "Bill Gates", "Age" : "60" };
var other = { "Name" : "Bill Gates", "Age" : "60" }; // legal?  what am I?

@gafter
Copy link
Member Author

gafter commented Nov 23, 2015

@HaloFour Good question. In @AnthonyDGreen 's prototype, it would look for any type named JObject in scope.

@HaloFour
Copy link

@gafter Very interesting. I'm curious if/how that might evolve into something a little more general purpose, unless the language team is comfortable making a loose dependency between C# and JSON.NET.

@alrz
Copy link
Member

alrz commented Nov 23, 2015

@gafter So these are literally json literals? I thought this would be a concise syntax for dictionaries and lists (by default) which can be used with other types like JObject as well. But now, if we want to use this for dictionaries we would be like,

using JObject = System.Collections.Generic.Dictionary<string,object>; // object?

and it doesn't even support type inference.

@HaloFour
Copy link

@alrz I'm gathering that the demonstrated prototype happened to behave in that way and that this proposal seeks to iterate on the underlying idea.

@alrz
Copy link
Member

alrz commented Nov 23, 2015

I think making lists and dictionaries a language feature (like tuples) with a special type and initialization syntax (which is not limited to Array and Dictionary<,>) would be a better approach so that constructing JObject objects will be just a special case of that.

// infers a (int: int)
var map = { 1 : 2 };

// equivalent to
Dictionary<int, int> map = new Dictionary<int, int> { { 1, 2 } };

// we can make them invocable so one
// can use the type to create an empty list
var list = int{}(); /* or */ {int}();

// existing array initializer
int[] arr = { 1, 2, 3 };
var arr = new[] { 1, 2, 3 };

// infers an int{}
var list = [1, 2, 3];

// equivalent to
var list = new List<int> { 1, 2, 3 };

// infers a JObject
JObject json = { "foo" : "bar" };
var json = new JObject { "foo" : "bar" };

// equivalent to
var json = new JObject { ["foo"] = "bar" };

In the last example, nested maps will infer the type of the parent, in this case JObject. For lists, the target would have an Add with the second parameter of a non-abstract type that implements IEnumerable like JArray to infer.

This has some interactions with #2319 so if you want to create another class you should be able to omit type parameters, like

KeyedCollection<,> collection = { ... };
SortedList<> list = [ ... ];

If the context already provides type information, such as a function argument or an already typed variable, you can create an empty list or dictionary with [] or {:} respectively.

By the way, depending on the name of JObject doesn't seem like a good idea, IMO.

@bondsbw
Copy link

bondsbw commented Nov 23, 2015

What about

var other = { "Name" : "Bill Gates", "Age" : 60 };

This couldn't map to IDictionary<string, string> because "Age" maps to an int. So, would its type be something like Tuple<Tuple<string, string>, Tuple<string, int>>?

@alrz
Copy link
Member

alrz commented Nov 23, 2015

@bondsbw You can already create a ((string, string), (string, int)) with (("Name", "Bill Gates"), ("Age", 60 )). And { "Name" : "Bill Gates", "Age" : 60 } would return a (string: object) by default. since object is the common type of the string and int — I'm assuming a more advanced type inference like one proposed at #1419. If you just want a JObject you can declare the variable like JObject other = .... I'm thinking that it doesn't need the new keyword as long as it's not ambiguous.

@bondsbw
Copy link

bondsbw commented Nov 23, 2015

@alrz Right, I'm just not sure I like how IDictionary<string, object> loses type information.

Preserves type info, but not JSON compatible:

var anonymous = new { Name = "Bill Gates", Age = 60 };
int age = anonymous.Age;

JSON-compatible syntax but loses type info:

var dict = { "Name" : "Bill Gates", "Age" : 60 }; // IDictionary<string, object>
int age = (int)dict["Age"]; // requires cast

JSON-compatible syntax that retains type info (I changed the inner Tuples to KeyValuePairs since that seems to be a clearer definition of their purpose):

var tuple = { "Name" : "Bill Gates", "Age" : 60 }; // Tuple<KeyValuePair<string, string>, KeyValuePair<string, int>>
int age = tuple.Item2.Value; // no cast required

The downside of the last option is that accessing the value via .Item2.Value is ugly. I would love to hear suggestions for fixing that, either inside the current language boundaries or with new syntax.

@alrz
Copy link
Member

alrz commented Nov 23, 2015

@bondsbw Returning a Tuple from the dictionary syntax makes no sense, beside of that, you lose hash table and all the goods that dictionary provides for key lookup, etc.

One option is extending IntelliSence to be able to suggest keys with #3555 syntax,

var json = { "Name" : "Bill Gates", "Age" : 60 };
int age = (int) json.$Age;

But still, you have to explicitly cast the returned object and also, if you pass it to another method, keys are not predictable. This is the same scenario mentioned in "wire formats" section at #3910.

how to support strongly typed logic over them without forcing them to be deserialized to strongly typed objects at runtime.

An interface-based solution is proposed but I don't know if that is considered or dropped.

PS: If you really want to use tuples you can write

var tuple = ( Name: "Bill Gates", Age: 60 );
int age = tuple.Age;

and no information shall be lost.

@bondsbw
Copy link

bondsbw commented Nov 23, 2015

@alrz You're right, the new Tuple syntax is a lot better than trying to get type safety from JSON literals. As much as I try to advocate for JSON literals, I just don't feel that they are worthwhile.

As a language extension (see #6972), JSON literals might make more sense. Not as part of C# proper though.

@alrz
Copy link
Member

alrz commented Nov 23, 2015

@bondsbw Not as "json literals" but as language integrated collection types they would.

@nerdshark
Copy link

What this request is suggesting is essentially special syntax for instantiating a hierarchical data structure whose values must be of a type specified in a given type collection. In functional programming languages (like F# and Nemerle), this is an extremely common pattern, and is supported through the use of abstract/algebraic data types (particularly the 'variant' or 'discriminated union') and pattern matching (when traversing the data structure). Using the above-specified syntax, could we not infer the types of the dictionary values and generate a DU to encapsulate the set of possible types a value may take? This obviously depends on ADT support becoming part of C# vNext, and I'll try to find the relevant proposal to link to in a bit.

Edit: Looking at Nemerle's implementation of variants may be useful, for gleaning implementation ideas. Essentially, variants are implemented as compiler macros which generate an abstract base class, and the range of possible values that the variant may take are nested child classes which inherit from the abstract parent. These nested child classes can be implemented as record types, which allow the author to use them to encapsulate various real values. An implementation similar to this would allow remaining backwards-compatible with older versions of C#, just as Nemerle variants are compatible (though somewhat unwieldly) with current C#.

Edit: Here's the relevant ADT proposal I mentioned earlier.

@KrisVandermotten
Copy link
Contributor

Please do not make the C# language rely on the presence of a type called JObject, especially since the Windows runtime has a type called JsonObject in Windows.Data.Json.

@AdamSpeight2008
Copy link
Contributor

@gafter
Why not prefix the json with a contextual keyword

var ja = json [ a, b, c ]; // json array
var jo = json {  }; // json object ` 

@gafter
Copy link
Member Author

gafter commented Feb 1, 2016

@AdamSpeight2008 are you proposing a new keyword? What are its semantics? How would that work in the recursive (nested) case?

@AdamSpeight2008
Copy link
Contributor

@gafter Yes, and also a bit of clarification on things within yours. :-)

The prefix for JSON makes sense as won't have the same grammar rules as C#, like VB's XML Literals. It just indicates a JSON block follows, Within it should follow the grammar rules for JSON.


How would you include values from a .net object? other than the literals?

var k = "Key";
var v = 42;
var j = json { k : v } ; // <-- ? wanting { "Key" : 42 }

Wouldn't this require some form of escaping with the literal? To be useful.

var j = json { @{ from  entry in dict
          where entry.value >= 18
          select key 
                }
             }; // Produce a JSON array ot the keys

@bondsbw
Copy link

bondsbw commented Feb 1, 2016

@AdamSpeight2008 I like that. And going further, I prefer it be extensible so that the language is not just catering to the flavor-of-the-day. I proposed a similar mechanism in #6972, and despite it being closed I still believe there is room for more than just an analyzer. Syntax highlighting and intellisense could be amazingly useful features for embedded DSLs.

Sure, this is all technically possible by customizing Roslyn. But that level of language change makes more sense when you want to fully customize C# by adding keywords, operators, expression types, and other deeply-integrated language features. Customizing Roslyn can also be quite complex, and does not provide a mechanism to embed multiple DSLs from separate sources.

Embedding a DSL inside a block is better for non-C# languages like data languages, or when the readability of the concept you want to express is hindered by the C# language.

Here is an example of a DSL I would like to be able to embed in C#, such that the result of the expression is transformed into a custom hierarchical data type that recognizes information such as level and section identifiers:

Table of Contents
    1. Before you begin
    2. Introduction
    3. Compiler concepts
        a. Lexer
        b. Parser
        c. Checker
        d. Emitter
    4. Creating your own language
        a. Design
        b. Optimization
        c. Tooling

Or perhaps the embedded DSL handler transforms it into a dictionary hierarchy, or whatever. And it can perform syntax highlighting, show syntax errors, provide intellisense, and all of the capabilities you might expect from modern language tooling.

C# as-is lacks the readability you could achieve with a DSL that is built for the purpose:

new TopLevelNode("Table of Contents",
    new SectionNode("1", "Before you begin"),
    new SectionNode("2", "Introduction"),
    new SectionNode("3", "Compiler concepts",
        new SectionNode("a", "Lexer"),
        new SectionNode("b", "Parser"),
        new SectionNode("c", "Checker"),
        new SectionNode("d", "Emitter")
    ),
    new SectionNode("4", "Creating your own language"
        new SectionNode("a", "Design"),
        new SectionNode("b", "Optimization"),
        new SectionNode("c", "Tooling")
    )
);

@gafter
Copy link
Member Author

gafter commented Feb 1, 2016

I am not aware of any suggestion to have it use a "different" grammar. It would be folded into the C# expression grammar.

@AdamSpeight2008
Copy link
Contributor

@gafter Just that the examples I seen use literals, hence looks like vanilla JSON .
Where I suspect that the user actually wants to construct a json thing (object / array) containing values being passed into it. Like you can do with XML Literals.

VB's JSON Literals

@gafter
Copy link
Member Author

gafter commented Feb 2, 2016

If you want the value of an expression in there you just write the expression

@nikeee
Copy link

nikeee commented Feb 13, 2016

These issues may contain also relevant points, although they're about XML:
#1746
#3912

@gafter
Copy link
Member Author

gafter commented Mar 27, 2017

We are now taking language feature discussion in other repositories:

Features that are under active design or development, or which are "championed" by someone on the language design team, have already been moved either as issues or as checked-in design documents. For example, the proposal in this repo "Proposal: Partial interface implementation a.k.a. Traits" (issue 16139 and a few other issues that request the same thing) are now tracked by the language team at issue 52 in https://github.com/dotnet/csharplang/issues, and there is a draft spec at https://github.com/dotnet/csharplang/blob/master/proposals/default-interface-methods.md and further discussion at issue 288 in https://github.com/dotnet/csharplang/issues. Prototyping of the compiler portion of language features is still tracked here; see, for example, https://github.com/dotnet/roslyn/tree/features/DefaultInterfaceImplementation and issue 17952.

In order to facilitate that transition, we have started closing language design discussions from the roslyn repo with a note briefly explaining why. When we are aware of an existing discussion for the feature already in the new repo, we are adding a link to that. But we're not adding new issues to the new repos for existing discussions in this repo that the language design team does not currently envision taking on. Our intent is to eventually close the language design issues in the Roslyn repo and encourage discussion in one of the new repos instead.

Our intent is not to shut down discussion on language design - you can still continue discussion on the closed issues if you want - but rather we would like to encourage people to move discussion to where we are more likely to be paying attention (the new repo), or to abandon discussions that are no longer of interest to you.

If you happen to notice that one of the closed issues has a relevant issue in the new repo, and we have not added a link to the new issue, we would appreciate you providing a link from the old to the new discussion. That way people who are still interested in the discussion can start paying attention to the new issue.

Also, we'd welcome any ideas you might have on how we could better manage the transition. Comments and discussion about closing and/or moving issues should be directed to #18002. Comments and discussion about this issue can take place here or on an issue in the relevant repo.


I am not moving this particular issue because I don't have confidence that the LDM would likely consider doing this. @AnthonyDGreen has volunteered to champion this feature request. If he does, please provide a link here to the appropriate repo.

@ghost
Copy link

ghost commented Feb 19, 2019

@gafter @alrz @bondsbw @nerdshark
I have a similar proposal here dotnet/csharplang#2247, to use the JSON intializer to allow intellisense for string keys defined in the intializer. Plase join the discussion and reconsider this proposal according to my goal.

@ghost ghost unassigned AnthonyDGreen Feb 19, 2019
@ghost
Copy link

ghost commented Feb 19, 2019

I saw this after the last comment:

MohammadHamdyGhanem unassigned AnthonyDGreen

I did not do such thing and I even don't know what it means!

@jmarolf
Copy link
Contributor

jmarolf commented Feb 19, 2019

@MohammadHamdyGhanem so this happens when the person assigned is no longer in the Microsoft github organization. Github unassigns them as soon as there is any change to the issue.

@ghost
Copy link

ghost commented Feb 19, 2019

@jmarolf
Thanks. But I cann't get it. When and where did I unassign AnthonyDGreen and what has this to do with this topic where I just post a comment that doesn't envolve him? I see AnthonyDGreen in VB.NET repo only. Just feeling it is a starnge statement! If this some sort of a bug in Github, please report it.
Thanks

@jmarolf
Copy link
Contributor

jmarolf commented Feb 19, 2019

If this some sort of a bug in Github, please report it.

This is a bug on github and it has been reported

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
4 - In Review A fix for the issue is submitted for review. Area-Language Design
Projects
None yet
Development

No branches or pull requests

10 participants