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

Add DynamicJson type #12843

Merged
merged 20 commits into from Jun 22, 2020
Merged

Add DynamicJson type #12843

merged 20 commits into from Jun 22, 2020

Conversation

pakrym
Copy link
Contributor

@pakrym pakrym commented Jun 17, 2020

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.

@pakrym pakrym requested a review from tg-msft June 17, 2020 17:47
Copy link
Member

@tg-msft tg-msft left a 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; }
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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
Copy link
Member

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.

Copy link
Contributor Author

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?

Copy link
Member

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.

Copy link
Contributor Author

@pakrym pakrym Jun 19, 2020

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.

Copy link
Member

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. 😄

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; }
Copy link
Member

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()?

Copy link
Contributor Author

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?

Copy link
Member

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; }
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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) { }
Copy link
Member

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?

Copy link
Contributor Author

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.

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; }
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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 SearchDocuments

Copy link
Contributor Author

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?

Copy link
Member

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"));
Copy link
Member

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.

Copy link
Member

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>
///
Copy link
Member

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?

Copy link
Member

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.

@pakrym
Copy link
Contributor Author

pakrym commented Jun 22, 2020

I'll address issues in a follow up PR.

@pakrym pakrym merged commit 1286a9d into Azure:master Jun 22, 2020
@pakrym pakrym deleted the pakrym/dynamicjson branch June 22, 2020 18:54
prmathur-microsoft pushed a commit that referenced this pull request Jul 8, 2020
Comment on lines +547 to +550
private long _long;
private bool _hasLong;
private double _double;
private bool _hasDouble;
Copy link
Member

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);
Copy link
Member

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());

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.

None yet

3 participants