-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Add DynamicJson type #12843
Add DynamicJson type #12843
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just adding some quick initial thoughts - I'll do a full review later tonight.
public System.Collections.Generic.IEnumerable<Azure.Core.DynamicJson> EnumerateArray() { throw null; } | ||
public System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, Azure.Core.DynamicJson>> EnumerateObject() { throw null; } | ||
public int GetArrayLength() { throw null; } | ||
public bool GetBoolean() { throw null; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this convert the current value to a boolean? As
might be a better prefix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was purposefully trying to mirror JsonElement: https://docs.microsoft.com/en-us/dotnet/api/system.text.json.jsonelement?view=netcore-3.1.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I wasn't really expecting that. When I see this API, I wonder why we don't just add a couple of extension methods to JsonElement
like Serialize<T>
, AsDynamic
, etc. I was thinking this type would be much smaller and focused exclusively on objects to avoid all these other accessors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This type can't be focused just on objects, services like digitaltwins support taking arbitrary JSON so the root can be an object or an array maybe even a primitive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And maybe we're trying to solve too much here? Maybe we should tell folks if you want to use primitives or arrays with DigitalTwins that you need to work with raw JSON elements. I don't think I'd want to use this type as it exists right now for Search because it's way too general and I think would confuse what's already a tricky part of the Search API.
@@ -24,6 +24,52 @@ namespace Azure.Core | |||
public override string ToString() { throw null; } | |||
public string ToString(System.Text.Encoding encoding) { throw null; } | |||
} | |||
public partial class DynamicJson : System.Dynamic.IDynamicMetaObjectProvider |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should also implement IDictionary
sooner than later because I think it'll help force some of the naming choices below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you feel strongly about having IDictionary and why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's such a well known, easy to use thing. This is getting to be a pretty hefty API at this point and if I'm trying to get a quick job done, seeing IDictionary may let me shortcut all the rest of it. All of https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.idictionary-2?view=netcore-3.1#extension-methods as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Problem with implementing IDictionary<string, DJ> is that I would also need to implement IList if the current value is an array. Then we get 2 IEnumerable interfaces on the same object and that's a bit weird.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're jamming an object and an array and primitives into the same type. It's already weird. 😄
sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs
Outdated
Show resolved
Hide resolved
public long GetLong() { throw null; } | ||
public Azure.Core.DynamicJson GetProperty(string name) { throw null; } | ||
public string? GetString() { throw null; } | ||
public static Azure.Core.DynamicJson Object() { throw null; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the signature, I don't really know what this API does. Should this just be Create()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. Any idea how to name the one that creates an array?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Create
vs CreateArray
?
public static Azure.Core.DynamicJson Serialize<T>(T value, Azure.Core.ObjectSerializer serializer) { throw null; } | ||
public static Azure.Core.DynamicJson Serialize<T>(T value, System.Text.Json.JsonSerializerOptions? options = null) { throw null; } | ||
System.Dynamic.DynamicMetaObject System.Dynamic.IDynamicMetaObjectProvider.GetMetaObject(System.Linq.Expressions.Expression parameter) { throw null; } | ||
public System.Text.Json.JsonElement ToJsonElement() { throw null; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would Serialize<object>()
do the same?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I understand. Serialize is a static method that takes an object and turns it into DJ.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, sorry, should have said Deserialize
. It feels like this is a short cut for calling Serialize<object>(this)
.
System.Dynamic.DynamicMetaObject System.Dynamic.IDynamicMetaObjectProvider.GetMetaObject(System.Linq.Expressions.Expression parameter) { throw null; } | ||
public System.Text.Json.JsonElement ToJsonElement() { throw null; } | ||
public override string ToString() { throw null; } | ||
public void WriteTo(System.Text.Json.Utf8JsonWriter writer) { } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be public?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not feeling strong about it. I was trying to have a lot of interop with S.T.J.
sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs
Outdated
Show resolved
Hide resolved
public string? GetString() { throw null; } | ||
public static Azure.Core.DynamicJson Object() { throw null; } | ||
public static Azure.Core.DynamicJson Object(System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, Azure.Core.DynamicJson>> values) { throw null; } | ||
public static explicit operator bool (Azure.Core.DynamicJson json) { throw null; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would love a virtual
I could override with domain specific conversion logic. We'd probably need that for Search.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would also require us to have a factory func so the child DynamicJson objects we create are of the right type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, and I'd like a virtual one of those as well please. SearchDocument
turns nested objects into more SearchDocument
s
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do you get an object nested into array via SearchDocument?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do that when parsing JSON. But if you just set a property, we leave your object type and let the JSON serializer deal with it when turning it back into JSON.
public void DynamicCanConvertToString() => Assert.AreEqual("string", JsonAsType<string>("\"string\"")); | ||
|
||
[Test] | ||
public void DynamicCanConvertToInt() => Assert.AreEqual(5, JsonAsType<int>("5")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a test making it explicit whether "\"5\""
converts to int
? That's been a source of unpleasantness in SearchDocument
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, this upcoming feature should help with that:
dotnet/runtime#30255
namespace Azure.Core | ||
{ | ||
/// <summary> | ||
/// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe Represents a JSON value returned from or provided to service operations. It can be used as either a dynamic object or via its accessors.
for now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, you didn't do any of these yet... 😄 It's probably okay for Experimental but it'd be good to fix them up as soon as folks are happy with the broad shape of the API.
I'll address issues in a follow up PR. |
private long _long; | ||
private bool _hasLong; | ||
private double _double; | ||
private bool _hasDouble; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why didn't you make these fields nullable?
public static DynamicJson Serialize<T>(T value, JsonSerializerOptions? options = null) | ||
{ | ||
var serialized = JsonSerializer.Serialize<T>(value, options); | ||
return new DynamicJson(JsonDocument.Parse(serialized).RootElement); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JsonDocument
is IDisposable
.
Who is responsible for disposing it? If you don't, APIs like these can accidentally exhaust the underlying array pool.
For your specific case, consider cloning the element to detach it from the document's lifetime, and dispose the document.
https://docs.microsoft.com/en-us/dotnet/api/system.text.json.jsonelement.clone?view=netcore-3.1
Something like this:
using doc = JsonDocument.Parse(serialized);
return new DynamicJson(doc.RootElement.Clone());
Adds an initial implementation, unit tests and perf tests.
The implementation is known to be terrible but I'd like to get something out and start getting feedback.