forked from MessagePack-CSharp/MessagePack-CSharp
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request MessagePack-CSharp#948 from AArnott/expandoObject
Add support for ExpandoObject
- Loading branch information
Showing
8 changed files
with
280 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# Using `ExpandoObject` for Javascript-like discovery of messagepack structures | ||
|
||
In Javascript an arbitrary JSON object can be parsed into an object graph and then explored with Javascript as easily as a native Javascript object, | ||
since in Javascript all property access is late-bound. | ||
In C# we can do the same thing using the `dynamic` keyword and the `ExpandoObject` type. | ||
|
||
By default, deserializing untyped maps results in a `Dictionary<object, object>` being created to store the map. | ||
If you would like to use C# `dynamic` to explore the deserialized object graph more naturally (i.e. the way Javascript would allow), | ||
you can deserialize these maps into .NET `ExpandoObject` and use the C# dynamic keyword: | ||
|
||
```cs | ||
dynamic expando = new ExpandoObject(); | ||
expando.Name = "George"; | ||
expando.Age = 18; | ||
expando.Other = new { OtherProperty = "foo" }; | ||
|
||
byte[] bin = MessagePackSerializer.Serialize(expando, MessagePackSerializerOptions.Standard); | ||
this.logger.WriteLine(MessagePackSerializer.ConvertToJson(bin)); // {"Name":"George","Age":18,"Other":{"OtherProperty":"foo"}} | ||
dynamic expando2 = MessagePackSerializer.Deserialize<ExpandoObject>(bin, ExpandoObjectResolver.Options); | ||
Assert.Equal(expando.Name, expando2.Name); | ||
Assert.Equal(expando.Age, expando2.Age); | ||
Assert.NotNull(expando2.Other); | ||
Assert.Equal(expando.Other.OtherProperty, expando2.Other.OtherProperty); | ||
``` |
58 changes: 58 additions & 0 deletions
58
src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Formatters/ExpandoObjectFormatter.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
// Copyright (c) All contributors. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
using System.Collections.Generic; | ||
using System.Dynamic; | ||
|
||
namespace MessagePack.Formatters | ||
{ | ||
public class ExpandoObjectFormatter : IMessagePackFormatter<ExpandoObject> | ||
{ | ||
public static readonly IMessagePackFormatter<ExpandoObject> Instance = new ExpandoObjectFormatter(); | ||
|
||
private ExpandoObjectFormatter() | ||
{ | ||
} | ||
|
||
public ExpandoObject Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) | ||
{ | ||
if (reader.TryReadNil()) | ||
{ | ||
return null; | ||
} | ||
|
||
var result = new ExpandoObject(); | ||
int count = reader.ReadMapHeader(); | ||
if (count > 0) | ||
{ | ||
IFormatterResolver resolver = options.Resolver; | ||
IMessagePackFormatter<string> keyFormatter = resolver.GetFormatterWithVerify<string>(); | ||
IMessagePackFormatter<object> valueFormatter = resolver.GetFormatterWithVerify<object>(); | ||
IDictionary<string, object> dictionary = result; | ||
|
||
options.Security.DepthStep(ref reader); | ||
try | ||
{ | ||
for (int i = 0; i < count; i++) | ||
{ | ||
string key = keyFormatter.Deserialize(ref reader, options); | ||
object value = valueFormatter.Deserialize(ref reader, options); | ||
dictionary.Add(key, value); | ||
} | ||
} | ||
finally | ||
{ | ||
reader.Depth--; | ||
} | ||
} | ||
|
||
return result; | ||
} | ||
|
||
public void Serialize(ref MessagePackWriter writer, ExpandoObject value, MessagePackSerializerOptions options) | ||
{ | ||
var dictionaryFormatter = options.Resolver.GetFormatterWithVerify<IDictionary<string, object>>(); | ||
dictionaryFormatter.Serialize(ref writer, value, options); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
57 changes: 57 additions & 0 deletions
57
src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/ExpandoObjectResolver.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// Copyright (c) All contributors. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
using System.Collections.Generic; | ||
using System.Dynamic; | ||
using MessagePack.Formatters; | ||
|
||
namespace MessagePack.Resolvers | ||
{ | ||
/// <summary> | ||
/// A resolver for use when deserializing MessagePack data where the schema is not known at compile-time | ||
/// such that strong-types can be instantiated. | ||
/// Instead, <see cref="ExpandoObject"/> is used wherever a MessagePack <em>map</em> is encountered. | ||
/// </summary> | ||
public static class ExpandoObjectResolver | ||
{ | ||
/// <summary> | ||
/// The resolver to use to deserialize into C#'s <c>dynamic</c> keyword. | ||
/// </summary> | ||
/// <remarks> | ||
/// This resolver includes more than just the <see cref="ExpandoObjectFormatter"/>. | ||
/// </remarks> | ||
public static readonly IFormatterResolver Instance = CompositeResolver.Create( | ||
new IMessagePackFormatter[] | ||
{ | ||
ExpandoObjectFormatter.Instance, | ||
new PrimitiveObjectWithExpandoMaps(), | ||
}, | ||
new IFormatterResolver[] { BuiltinResolver.Instance }); | ||
|
||
/// <summary> | ||
/// A set of options that includes the <see cref="Instance"/> | ||
/// and puts the deserializer into <see cref="MessagePackSecurity.UntrustedData"/> mode. | ||
/// </summary> | ||
public static readonly MessagePackSerializerOptions Options = MessagePackSerializerOptions.Standard | ||
.WithSecurity(MessagePackSecurity.UntrustedData) // when the schema isn't known beforehand, that generally suggests you don't know/trust the data. | ||
.WithResolver(Instance); | ||
|
||
private class PrimitiveObjectWithExpandoMaps : PrimitiveObjectFormatter | ||
{ | ||
protected override object DeserializeMap(ref MessagePackReader reader, int length, MessagePackSerializerOptions options) | ||
{ | ||
IMessagePackFormatter<string> keyFormatter = options.Resolver.GetFormatterWithVerify<string>(); | ||
IMessagePackFormatter<object> objectFormatter = options.Resolver.GetFormatter<object>(); | ||
IDictionary<string, object> dictionary = new ExpandoObject(); | ||
for (int i = 0; i < length; i++) | ||
{ | ||
var key = keyFormatter.Deserialize(ref reader, options); | ||
var value = objectFormatter.Deserialize(ref reader, options); | ||
dictionary.Add(key, value); | ||
} | ||
|
||
return dictionary; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
108 changes: 108 additions & 0 deletions
108
src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/ExpandoObjectTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
// Copyright (c) All contributors. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
using System.Dynamic; | ||
using System.Runtime.Serialization; | ||
using MessagePack.Resolvers; | ||
using Xunit; | ||
using Xunit.Abstractions; | ||
|
||
namespace MessagePack.Tests | ||
{ | ||
public class ExpandoObjectTests | ||
{ | ||
private readonly ITestOutputHelper logger; | ||
|
||
public ExpandoObjectTests(ITestOutputHelper logger) | ||
{ | ||
this.logger = logger; | ||
} | ||
|
||
[Fact] | ||
public void ExpandoObject_Roundtrip() | ||
{ | ||
var options = MessagePackSerializerOptions.Standard; | ||
|
||
dynamic expando = new ExpandoObject(); | ||
expando.Name = "George"; | ||
expando.Age = 18; | ||
|
||
byte[] bin = MessagePackSerializer.Serialize(expando, options); | ||
this.logger.WriteLine(MessagePackSerializer.ConvertToJson(bin)); | ||
|
||
dynamic expando2 = MessagePackSerializer.Deserialize<ExpandoObject>(bin, options); | ||
Assert.Equal(expando.Name, expando2.Name); | ||
Assert.Equal(expando.Age, expando2.Age); | ||
} | ||
|
||
[Fact] | ||
public void ExpandoObject_DeepGraphContainsAnonymousType() | ||
{ | ||
dynamic expando = new ExpandoObject(); | ||
expando.Name = "George"; | ||
expando.Age = 18; | ||
expando.Other = new { OtherProperty = "foo" }; | ||
|
||
byte[] bin = MessagePackSerializer.Serialize(expando, MessagePackSerializerOptions.Standard); | ||
this.logger.WriteLine(MessagePackSerializer.ConvertToJson(bin)); | ||
|
||
dynamic expando2 = MessagePackSerializer.Deserialize<ExpandoObject>(bin, ExpandoObjectResolver.Options); | ||
Assert.Equal(expando.Name, expando2.Name); | ||
Assert.Equal(expando.Age, expando2.Age); | ||
Assert.NotNull(expando2.Other); | ||
Assert.Equal(expando.Other.OtherProperty, expando2.Other.OtherProperty); | ||
} | ||
|
||
[Fact] | ||
public void ExpandoObject_DeepGraphContainsCustomTypes() | ||
{ | ||
var options = MessagePackSerializerOptions.Standard; | ||
var f = options.Resolver.GetFormatter<string>(); | ||
|
||
dynamic expando = new ExpandoObject(); | ||
expando.Name = "George"; | ||
expando.Age = 18; | ||
expando.Other = new CustomObject { OtherProperty = "foo" }; | ||
|
||
byte[] bin = MessagePackSerializer.Serialize(expando, MessagePackSerializerOptions.Standard); | ||
this.logger.WriteLine(MessagePackSerializer.ConvertToJson(bin)); | ||
|
||
dynamic expando2 = MessagePackSerializer.Deserialize<ExpandoObject>(bin, ExpandoObjectResolver.Options); | ||
Assert.Equal(expando.Name, expando2.Name); | ||
Assert.Equal(expando.Age, expando2.Age); | ||
Assert.NotNull(expando2.Other); | ||
Assert.Equal(expando.Other.OtherProperty, expando2.Other.OtherProperty); | ||
} | ||
|
||
#if !UNITY_2018_3_OR_NEWER | ||
|
||
[Fact] | ||
public void ExpandoObject_DeepGraphContainsCustomTypes_TypeAnnotated() | ||
{ | ||
var options = MessagePackSerializerOptions.Standard.WithResolver(TypelessObjectResolver.Instance); | ||
|
||
dynamic expando = new ExpandoObject(); | ||
expando.Name = "George"; | ||
expando.Age = 18; | ||
expando.Other = new CustomObject { OtherProperty = "foo" }; | ||
|
||
byte[] bin = MessagePackSerializer.Serialize(expando, options); | ||
this.logger.WriteLine(MessagePackSerializer.ConvertToJson(bin)); | ||
|
||
dynamic expando2 = MessagePackSerializer.Deserialize<ExpandoObject>(bin, options); | ||
Assert.Equal(expando.Name, expando2.Name); | ||
Assert.Equal(expando.Age, expando2.Age); | ||
Assert.IsType<CustomObject>(expando2.Other); | ||
Assert.Equal(expando.Other.OtherProperty, expando2.Other.OtherProperty); | ||
} | ||
|
||
#endif | ||
|
||
[DataContract] | ||
public class CustomObject | ||
{ | ||
[DataMember] | ||
public string OtherProperty { get; set; } | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters