Reputation: 1053
I am using Json.NET to serialize objects (commands) and send them via service bus. Some of the serialized objects gets too large to be send via service bus. I want to tag some properties with a custom attribute like TooLargeForServiceBusAttribute
. Now I want to rename these properties in the serialized json and replace the value. The goal is to swap the large content of the property to an external store and add the id of the content in the external store to the serialized json string.
Example
class CommandWithTooLargeProperty
{
[TooLargeForServiceBus]
public string SomeProperty { get; set; }
}
I want the serialized json to be as follows:
{
SomeProperty_EXTERNAL_STORE_ID = '10000000-2000-3000-4000-500000000000'
}
How can I hook into the serialization process of Json.NET to get what I want? I cannot use a custom converter because some of my command classes are already decorated with a custom converter and my described mechanism has to be transparent for every class I am serializing.
My first though was to write a class inheriting from JsonTextWriter
to rename the property but I do not want to rename every property but only the properties which are decorated with TooLargeForServiceBusAttribute
and the JsonTextWriter
I do not have access to the property of the source object.
And I have to inject something like IExternalStore
into the serialization pipeline to save the content of the swapped properties to an external store.
The deserialization process has to do the reverse work of the serialization: taking the id from SomeProperty_EXTERNAL_STORE_ID
, loading the content from IExternalStore
and setting the loaded value to the property of the deserialized object.
Upvotes: 0
Views: 2526
Reputation: 1053
I found a solution:
Implement your own ContractResolver
(named ContractResolverWithSwapPropertyValueSupport
in my example below).
In the resolver override CreateObjectContract
and set the JsonProperty
of the original property to Ignored = true
.
Add a new JsonProperty
with a custom IValueProvider
. This property will store the id of the swapped value in the external storage.
class Program
{
static void Main(string[] args)
{
var externalStorage = new SimpleDictionaryStorage();
var contractResolver = new ContractResolverWithSwapPropertyValueSupport(externalStorage);
var serializer = JsonSerializer.Create(new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All, // Allows deserializing to the actual runtime type
TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple, // In a version resilient way
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore, // Remove null value properties from the serialized object
Formatting = Formatting.Indented,
ContractResolver = contractResolver
});
var command = new CommandWithLargeProperty()
{
SomeLargeProperty = "Hello World"
};
string serializedCommand = null;
using (var stringWriter = new StringWriter())
using (var jsonWriter = new JsonTextWriter(stringWriter))
{
serializer.Serialize(jsonWriter, command);
serializedCommand = stringWriter.ToString();
}
Console.WriteLine(serializedCommand);
CommandWithLargeProperty deserializedCommand = null;
using (var stringReader = new StringReader(serializedCommand))
using (var jsonReader = new JsonTextReader(stringReader))
{
deserializedCommand = (CommandWithLargeProperty)serializer.Deserialize(jsonReader);
}
Console.WriteLine(command.SomeLargeProperty);
Console.WriteLine(deserializedCommand.SomeLargeProperty);
Console.WriteLine(command.SomeLargeProperty == deserializedCommand.SomeLargeProperty);
Console.ReadLine();
}
}
public class CommandWithLargeProperty
{
[SwapPropertyValue]
public string SomeLargeProperty { get; set; }
}
public class ContractResolverWithSwapPropertyValueSupport : DefaultContractResolver
{
private readonly IExternalStorage _externalValueStorage;
public ContractResolverWithSwapPropertyValueSupport(IExternalStorage externalValueStorage)
{
_externalValueStorage = externalValueStorage;
}
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var objectContract = base.CreateObjectContract(objectType);
foreach (var jsonProperty in objectContract.Properties.ToArray())
{
var propertyInfo = jsonProperty.DeclaringType.GetProperties().SingleOrDefault(p => p.Name == jsonProperty.UnderlyingName);
if (propertyInfo.GetCustomAttributes(typeof(SwapPropertyValueAttribute), inherit: false).SingleOrDefault() != null)
{
// Ignore the property which will be swapped
jsonProperty.Ignored = true;
// Add a new property with a ValueProvider which will swap the content of the real property to an external storage and vice versa
objectContract.Properties.Add(new JsonProperty()
{
PropertyName = string.Format("{0}_EXTERNAL_ID", jsonProperty.PropertyName),
PropertyType = typeof(Guid),
ValueProvider = new ExternalStorageValueProvider(_externalValueStorage, propertyInfo),
Readable = true,
Writable = true
});
}
}
return objectContract;
}
}
public class ExternalStorageValueProvider : IValueProvider
{
private readonly IExternalStorage _externalValueStorage;
private readonly PropertyInfo _propertyInfo;
public ExternalStorageValueProvider(IExternalStorage externalValueStorage, PropertyInfo propertyInfo)
{
_externalValueStorage = externalValueStorage;
_propertyInfo = propertyInfo;
}
public object GetValue(object target)
{
object valueRaw = _propertyInfo.GetValue(target);
string valueJson = JsonConvert.SerializeObject(valueRaw);
Guid id = _externalValueStorage.SetValue(valueJson);
return id;
}
public void SetValue(object target, object value)
{
Guid id = (Guid)value;
string valueJson = _externalValueStorage.GetValue(id);
object valueRaw = JsonConvert.DeserializeObject(valueJson);
_propertyInfo.SetValue(target, valueRaw);
}
}
public interface IExternalStorage
{
Guid SetValue(string objectAsJson);
string GetValue(Guid id);
}
public class SimpleDictionaryStorage : IExternalStorage
{
private readonly Dictionary<Guid, string> _store = new Dictionary<Guid, string>();
public Guid SetValue(string objectAsJsonString)
{
Guid id = Guid.NewGuid();
_store[id] = objectAsJsonString;
return id;
}
public string GetValue(Guid id)
{
return _store[id];
}
}
public class SwapPropertyValueAttribute : Attribute
{
}
Upvotes: 1