Reputation: 23
So, - in my DocumentDB I may have the following document:
{
"id": 1,
"type": "A",
"content": {
"x": 1,
"y": 2
}
}
That may be backed by this model:
public class acontent
{
public int x { get; set; }
public int y { get; set; }
}
public class document
{
public int id { get; set; }
public string type { get; set; }
public object content { get; set; }
}
public class documenta : document
{
public new acontent content { get; set; }
}
The idea here is that document is a complex object where content may vary depending on type.
Now, - in my ServiceFabric application I have a stateless microservice that reads from DocumentDB and should return a document type object when called from the ServiceProxy.
The problem in this is that the DocumentQuery from the DocumentDB SDK, uses Json.NET serializer when querying the database, whilst servicefabric uses DataContractSerializer for serializing the service-messages.
So when the content part of document class is being deserialized from the DocumentDB it becomes:
Newtonsoft.Json.Linq.JObject
But when it is serialized back through the returned service-message you get the exception:
Type 'Newtonsoft.Json.Linq.JToken' is a recursive collection data contract which is not supported. Consider modifying the definition of collection 'Newtonsoft.Json.Linq.JToken' to remove references to itself.
To illustrate this issue try the folowing code:
using System;
using System.IO;
using System.Text;
using System.Runtime.Serialization.Json;
using Newtonsoft.Json;
namespace jsoinissue
{
public class acontent
{
public int x { get; set; }
public int y { get; set; }
}
public class document
{
public int id { get; set; }
public string type { get; set; }
public object content { get; set; }
}
public class documenta : document
{
public new acontent content { get; set; }
}
public class Program
{
private const string JSON_A = "{\"id\":1,\"type\":\"A\",\"content\":{\"x\":1,\"y\":2}}";
private static string SerializeObject<T> (T obj)
{
try
{
DataContractJsonSerializer js = new DataContractJsonSerializer(typeof(T));
using (var ms = new MemoryStream())
{
js.WriteObject(ms, obj);
ms.Position = 0;
using (var sr = new StreamReader(ms))
return sr.ReadToEnd();
}
}
catch (Exception e)
{
return String.Format("EXCEPTION: {0}",e.Message);
}
}
public static void Main()
{
var A = JsonConvert.DeserializeObject<document>(JSON_A);
var a = SerializeObject<document>(A);//HERE BE TROUBLE
Console.WriteLine(a);
Console.ReadKey();
}
}
}
How could I best resolve this issue?
Upvotes: 0
Views: 620
Reputation: 2599
Have you looked into changing away from DataContractSerializer to a serializer with better support instead? Here's how you'd plug in a different serializer.
class InitializationCallbackAdapter
{
public Task OnInitialize()
{
this.StateManager.TryAddStateSerializer(new MyStateSerializer());
return Task.FromResult(true);
}
public IReliableStateManager StateManager { get; set; }
}
class MyStatefulService : StatefulService
{
public MyStatefulService(StatefulServiceContext context)
: this(context, new InitializationCallbackAdapter())
{
}
public MyStatefulService(StatefulServiceContext context, InitializationCallbackAdapter adapter)
: base(context, new ReliableStateManager(context, new ReliableStateManagerConfiguration(onInitializeStateSerializersEvent: adapter.OnInitialize)))
{
adapter.StateManager = this.StateManager;
}
}
This could be newtonsoft or whatever. Also I believe that the method is currently marked "Deprecated" however there's no alternative, so if it solves your problem go ahead and use it.
Upvotes: 0
Reputation: 116805
Your basic problem is that DataContractJsonSerializer
does not support untyped, free-form JSON data. As explained in Working with untyped JSON in a WCF service the System.Json
namespace was added to Silverlight for this purpose, but it seems that it never made it into the full .Net class library.
Instead, in your stateless microservice can do a nested serialization where the free-form JSON is represented as an escaped string literal when serializing using the data contract serializer. Thus your classes would look something like this:
[DataContract]
[JsonObject]
public abstract class documentbase
{
[DataMember]
[JsonProperty]
public int id { get; set; }
[DataMember]
[JsonProperty]
public string type { get; set; }
[IgnoreDataMember]
[JsonProperty("content")]
public abstract JToken JsonContent { get; set; }
[JsonIgnore]
[DataMember(Name = "content")]
string DataContractContent
{
get
{
if (JsonContent == null)
return null;
return JsonContent.ToString(Newtonsoft.Json.Formatting.None);
}
set
{
if (string.IsNullOrEmpty(value))
JsonContent = null;
else
JsonContent = JToken.Parse(value);
}
}
}
[DataContract]
[JsonObject]
public class document : documentbase
{
JToken content;
public override JToken JsonContent { get { return content; } set { content = value; } }
}
[DataContract]
[JsonObject]
public class document<T> : documentbase where T : class
{
[IgnoreDataMember]
[JsonIgnore]
public T Content { get; set; }
public override JToken JsonContent
{
get
{
if (Content == null)
return null;
return JToken.FromObject(Content);
}
set
{
if (value == null || value.Type == JTokenType.Null)
Content = null;
else
Content = value.ToObject<T>();
}
}
}
Then the JSON generated by SerializeObject<document>(A)
will look like:
{
"content":"{\"x\":1,\"y\":2}",
"id":1,
"type":"A"
}
Then, on the receiving system, you can deserialize to a document
using the data contract serializer, then query the deserialized JToken JsonContent
with LINQ to JSON. Alternatively, if the receiving system knows to expect a document<acontent>
it can deserialize the data contract JSON as such, since document
and document<T>
have identical data contracts.
Upvotes: 1