Can Json.NET deserialize a flattened JSON string with dot notation? - serialization

I have a flattened JSON:
{
"CaseName" : "John Doe v. State",
"CaseDate" : "<some date>",
"Client.FirstName" : "John",
"Client.LastName" : "Doe",
"Client.Email" : "johndoe#gmail.com"
etc...
}
I want to deserialize it back to this entity:
public class Case()
{
public string CaseName { get; set; }
public string CaseDate { get; set; }
public Client Client { get; set; }
}
where Client.FirstName, Client.LastName, and Client.Email are properties in the Client object. Using Json.NET, is there any way to get it to parse the dot notation and deserialize this entity correctly? Currently, using the default settings, it tells me that Client.FirstName is not a property in type Case.

Yes, you can. You would derive a class from JsonConverter and override the CanConvert method to indicae that you can convert the Client type.
Then, you would override the ReadJson and WriteJson methods to read and write the fields of the JSON literal.
For a JSON literal like this, it's more than likely you will need to create a JsonConverter for the Case type, as you will need to cache all the properties of the Client object during serialization until you have enough information to actually create the Client instance.
Then, you would call the Add method on the JsonConverterCollection instance exposed by the Converters property on the JsonSerializer instance you are using to perform your serialization/deserialization.
Note that if you need to do this for a number of different classes that might be represented in this manner, then you can write one JsonConverter implementation, and have it scan for an attribute on the property. If the property has the attribute and exposes another object with properties, it would expect to read/write the dot-notation.
It should be noted that while you are using the dot-notation for the identifier, it's very uncommon to do so. If possible, on the side that is constructing the JSON literal, it should be doing it in this manner:
{
CaseName: "John Doe v. State",
CaseDate: "<some date>",
Client:
{
FirstName: "John",
LastName: "Doe",
Email: "johndoe#gmail.com"
}
}
But that's assuming that you have control over that end. If you don't, then there's not much you can do.
If you do have control, then constructing your JSON literals in that manner would negate the need for a custom JsonConverter implementation.

simple code
public class SimpleDotJsonPropertyConverter : JsonConverter
{
public const string SplitChar = ".";
private readonly NamingStrategy _namingStrategy;
public SimpleDotJsonPropertyConverter() : this(new DefaultNamingStrategy())
{
}
public SimpleDotJsonPropertyConverter(Type namingStrategyType) : this(namingStrategyType.CreateInstance() as NamingStrategy)
{
}
public SimpleDotJsonPropertyConverter(NamingStrategy namingStrategy)
{
_namingStrategy = namingStrategy;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null)
{
writer.WriteRaw("{}");
return;
}
writer.WriteStartObject();
var type = value.GetType();
var props = type.GetPublicProperties();
foreach (var p in props)
{
if (p.PropertyType.IsValueType || p.PropertyType == typeof(string))
{
writer.WritePropertyName(_namingStrategy.GetPropertyName(p.Name, false));
writer.WriteValue(p.GetValue(value));
}
else
{
var parentName = p.Name;
var subProps = p.PropertyType.GetPublicProperties();
var memberValue = p.GetValue(value);
foreach (var sp in subProps)
{
writer.WritePropertyName(_namingStrategy.GetPropertyName(parentName, false) + SplitChar + _namingStrategy.GetPropertyName(sp.Name, false));
writer.WriteValue(sp.GetValue(memberValue));
}
}
}
writer.WriteEndObject();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null) return null;
var jsonObject = JObject.Load(reader);
var result = objectType.CreateInstance();
foreach (var item in jsonObject)
{
if (item.Key.IsNullOrEmpty()) continue;
var nameArrays = item.Key.Split(SplitChar, StringSplitOptions.RemoveEmptyEntries);
if (nameArrays.Length <= 1)
{
var p = objectType.GetProperty(item.Key, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
if (p != null)
{
if (item.Value == null)
p.SetValue(result, objectType.GetDefaultValue());
else
p.SetValue(result, item.Value.ToObject(p.PropertyType));
}
}
else
{
var name = nameArrays[0];
var subName = nameArrays[1];
var p = objectType.GetProperty(name, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
if (p == null) continue;
var subProp = p.PropertyType.GetProperty(subName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
if (subProp == null) continue;
if (!subProp.PropertyType.IsValueType && subProp.PropertyType != typeof(string)) throw new NotSupportedException("Nested complex objects are not supported ");
var member = p.GetValue(result);
if (member == null)
{
member = p.PropertyType.CreateInstance();
p.SetValue(result, member);
}
if (item.Value == null)
subProp.SetValue(member, objectType.GetDefaultValue());
else
subProp.SetValue(member, item.Value.ToObject(subProp.PropertyType));
}
}
return result;
}
public override bool CanConvert(Type objectType) => objectType.IsClass;
}

Although only half the problem (i.e. not helping with the fact your object has been flattened)
You can deal with dot notation in a very quick and dirty way with a simple
MyTargetClass retVal
= JsonConvert.DeserializeObject<MyTargetClass>(jsonAsString.Replace(".", "_"));
In combo with appropriate "_" property names on the MyTargetClass e.g.
public class MyTargetClass
{
public string CaseName {get; set;}
public DateTime CaseDate {get; set;}
public string Client_FirstName {get; set;}
//Other Properties
}

Related

Deserialize enums using the EnumMember value in AspNet [FromQuery] model binding

I have an endpoint in .NET 6 Microsoft.NET.Sdk.Web project that deserialize query strings into a .NET object by using the standard [FromQuery]
[Route("[controller]")]
public class SamplesController
: ControllerBase
{
[HttpGet]
public IActionResult Get([FromQuery]QueryModel queryModel)
{
if (!queryModel.Status.HasValue)
{
return BadRequest("Problem in deserialization");
}
return Ok(queryModel.Status.Value.GetEnumDisplayName());
}
}
The model contains an enum
public class QueryModel
{
/// <summary>
/// The foo parameter
/// </summary>
/// <example>bar</example>
public string? Foo { get; init; } = null;
/// <summary>
/// The status
/// </summary>
/// <example>on-hold</example>
public Status? Status { get; set; } = null;
}
And the enum has EnumMember attributes which value I want to use to deserialize from.
public enum Status
{
[EnumMember(Value = "open")]
Open,
[EnumMember(Value = "on-hold")]
OnHold
}
By default, .NET 6 does not take into consideration the EnumMember when deserializing.
The goal is to be able to send requests such as
http://localhost:5000/Samples?Foo=bar&Status=on-hold
and have the controller's action deserialize the QueryModel with the proper Status.OnHold value by using its EnumMember
I have tried without luck an extensions library that contains a converter, but the converter is not getting triggered when using [FromQuery]. See https://github.com/Macross-Software/core/issues/30
I have added a project to reproduce problem and as a sandbox to provide a solution**
https://gitlab.com/sunnyatticsoftware/issues/string-to-enum-mvc/-/tree/feature/1-original-problem
NOTE: I would need a solution where the Enum and the does not require any external dependency (just .NET sdk).
A custom Enum converter might be your choice. By leveraging the existing EnumConverter class what we need is to have a customized ConvertFrom method:
public class CustomEnumConverter : EnumConverter
{
public CustomEnumConverter([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicFields)] Type type) : base(type)
{
}
public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{
if (value is string strValue)
{
try
{
foreach (var name in Enum.GetNames(EnumType))
{
var field = EnumType.GetField(name);
if (field != null)
{
var enumMember = (EnumMemberAttribute)(field.GetCustomAttributes(typeof(EnumMemberAttribute), true).Single());
if (strValue.Equals(enumMember.Value, StringComparison.OrdinalIgnoreCase))
{
return Enum.Parse(EnumType, name, true);
}
}
}
}
catch (Exception e)
{
throw new FormatException((string)value, e);
}
}
return base.ConvertFrom(context, culture, value);
}
}
And then decorate the converter to your Model class:
[TypeConverter(typeof(CustomEnumConverter))]
public enum Status
{
[EnumMember(Value = "open")]
Open,
[EnumMember(Value = "on-hold")]
OnHold
}
then we can get the "on-hold" parsed. You might also want to override the ConverTo() for printing the EnumMember value to swagger. It is a bit hacky, but if you want a pure .NET solution this should be one of the minimal viable solutions.
Following the documentation guide Custom Model Binding in ASP.NET Core, you can create your own versions of Microsoft's classes EnumTypeModelBinderProvider, EnumTypeModelBinder (and base class SimpleTypeModelBinder) that replace incoming enum value names that have been renamed via EnumMemberAttribute with the original enum names before binding:
// Begin code for enum model binding
public class EnumMemberEnumTypeModelBinderProvider : IModelBinderProvider
{
public EnumMemberEnumTypeModelBinderProvider(MvcOptions options) { }
public IModelBinder? GetBinder(ModelBinderProviderContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
if (context.Metadata.IsEnum)
{
var enumType = context.Metadata.UnderlyingOrModelType;
Debug.Assert(enumType.IsEnum);
var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
if (EnumExtensions.TryGetEnumMemberOverridesToOriginals(enumType, out var overridesToOriginals))
return new EnumMemberEnumTypeModelBinder(suppressBindingUndefinedValueToEnumType: true, enumType, loggerFactory, overridesToOriginals);
}
return null;
}
}
public class EnumMemberEnumTypeModelBinder : ExtensibleSimpleTypeModelBinder
{
// Adapted from https://github.com/dotnet/aspnetcore/blob/c85baf8db0c72ae8e68643029d514b2e737c9fae/src/Mvc/Mvc.Core/src/ModelBinding/Binders/EnumTypeModelBinder.cs#L58
readonly Type enumType;
readonly bool isFlagged;
readonly Dictionary<ReadOnlyMemory<char>, string> overridesToOriginals;
readonly TypeConverter typeConverter;
public EnumMemberEnumTypeModelBinder(bool suppressBindingUndefinedValueToEnumType, Type modelType, ILoggerFactory loggerFactory, Dictionary<ReadOnlyMemory<char>, string> overridesToOriginals) : base(modelType, loggerFactory)
{
this.enumType = Nullable.GetUnderlyingType(modelType) ?? modelType;
if (!this.enumType.IsEnum)
throw new ArgumentException();
this.isFlagged = Attribute.IsDefined(enumType, typeof(FlagsAttribute));
this.overridesToOriginals = overridesToOriginals ?? throw new ArgumentNullException(nameof(overridesToOriginals));
this.typeConverter = TypeDescriptor.GetConverter(this.enumType);
}
protected override string? GetValueFromBindingContext(ValueProviderResult valueProviderResult) =>
EnumExtensions.ReplaceRenamedEnumValuesToOriginals(base.GetValueFromBindingContext(valueProviderResult), isFlagged, overridesToOriginals);
protected override void CheckModel(ModelBindingContext bindingContext, ValueProviderResult valueProviderResult, object? model)
{
if (model == null)
{
base.CheckModel(bindingContext, valueProviderResult, model);
}
else if (IsDefinedInEnum(model, bindingContext))
{
bindingContext.Result = ModelBindingResult.Success(model);
}
else
{
bindingContext.ModelState.TryAddModelError(
bindingContext.ModelName,
bindingContext.ModelMetadata.ModelBindingMessageProvider.ValueIsInvalidAccessor(
valueProviderResult.ToString()));
}
}
private bool IsDefinedInEnum(object model, ModelBindingContext bindingContext)
{
// Adapted from https://github.com/dotnet/aspnetcore/blob/c85baf8db0c72ae8e68643029d514b2e737c9fae/src/Mvc/Mvc.Core/src/ModelBinding/Binders/EnumTypeModelBinder.cs#L58
var modelType = bindingContext.ModelMetadata.UnderlyingOrModelType;
// Check if the converted value is indeed defined on the enum as EnumTypeConverter
// converts value to the backing type (ex: integer) and does not check if the value is defined on the enum.
if (bindingContext.ModelMetadata.IsFlagsEnum)
{
var underlying = Convert.ChangeType(
model,
Enum.GetUnderlyingType(modelType),
CultureInfo.InvariantCulture).ToString();
var converted = model.ToString();
return !string.Equals(underlying, converted, StringComparison.OrdinalIgnoreCase);
}
return Enum.IsDefined(modelType, model);
}
}
public class ExtensibleSimpleTypeModelBinder : IModelBinder
{
// Adapted from https://github.com/dotnet/aspnetcore/blob/c85baf8db0c72ae8e68643029d514b2e737c9fae/src/Mvc/Mvc.Core/src/ModelBinding/Binders/SimpleTypeModelBinder.cs
private readonly TypeConverter _typeConverter;
private readonly ILogger _logger;
public ExtensibleSimpleTypeModelBinder(Type type, ILoggerFactory loggerFactory) : this(type, loggerFactory, null) { }
public ExtensibleSimpleTypeModelBinder(Type type, ILoggerFactory loggerFactory, TypeConverter? typeConverter)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
if (loggerFactory == null)
throw new ArgumentNullException(nameof(loggerFactory));
_typeConverter = typeConverter ?? TypeDescriptor.GetConverter(type);
_logger = loggerFactory.CreateLogger<ExtensibleSimpleTypeModelBinder>();
}
protected virtual string? GetValueFromBindingContext(ValueProviderResult valueProviderResult) => valueProviderResult.FirstValue;
/// <inheritdoc />
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
//_logger.AttemptingToBindModel(bindingContext);
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult == ValueProviderResult.None)
{
//_logger.FoundNoValueInRequest(bindingContext);
// no entry
//_logger.DoneAttemptingToBindModel(bindingContext);
return Task.CompletedTask;
}
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
try
{
var value = GetValueFromBindingContext(valueProviderResult);
object? model;
if (bindingContext.ModelType == typeof(string))
{
// Already have a string. No further conversion required but handle ConvertEmptyStringToNull.
if (bindingContext.ModelMetadata.ConvertEmptyStringToNull && string.IsNullOrWhiteSpace(value))
model = null;
else
model = value;
}
else if (string.IsNullOrWhiteSpace(value))
{
// Other than the StringConverter, converters Trim() the value then throw if the result is empty.
model = null;
}
else
{
model = _typeConverter.ConvertFrom(context: null,culture: valueProviderResult.Culture, value: value);
}
CheckModel(bindingContext, valueProviderResult, model);
//_logger.DoneAttemptingToBindModel(bindingContext);
return Task.CompletedTask;
}
catch (Exception exception)
{
var isFormatException = exception is FormatException;
if (!isFormatException && exception.InnerException != null)
{
// TypeConverter throws System.Exception wrapping the FormatException,
// so we capture the inner exception.
exception = System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(exception.InnerException).SourceException;
}
bindingContext.ModelState.TryAddModelError(bindingContext.ModelName,exception, bindingContext.ModelMetadata);
// Were able to find a converter for the type but conversion failed.
return Task.CompletedTask;
}
}
/// <inheritdoc/>
protected virtual void CheckModel(
ModelBindingContext bindingContext,
ValueProviderResult valueProviderResult,
object? model)
{
// When converting newModel a null value may indicate a failed conversion for an otherwise required
// model (can't set a ValueType to null). This detects if a null model value is acceptable given the
// current bindingContext. If not, an error is logged.
if (model == null && !bindingContext.ModelMetadata.IsReferenceOrNullableType)
{
bindingContext.ModelState.TryAddModelError(
bindingContext.ModelName,
bindingContext.ModelMetadata.ModelBindingMessageProvider.ValueMustNotBeNullAccessor(
valueProviderResult.ToString()));
}
else
{
bindingContext.Result = ModelBindingResult.Success(model);
}
}
}
// End code for enum model binding
/********************************************************/
// Begin general enum parsing code
public class CharMemoryComparer : IEqualityComparer<ReadOnlyMemory<char>>
{
public static CharMemoryComparer OrdinalIgnoreCase { get; } = new CharMemoryComparer(StringComparison.OrdinalIgnoreCase);
public static CharMemoryComparer Ordinal { get; } = new CharMemoryComparer(StringComparison.Ordinal);
readonly StringComparison comparison;
CharMemoryComparer(StringComparison comparison) => this.comparison = comparison;
public bool Equals(ReadOnlyMemory<char> x, ReadOnlyMemory<char> y) => MemoryExtensions.Equals(x.Span, y.Span, comparison);
public int GetHashCode(ReadOnlyMemory<char> obj) => String.GetHashCode(obj.Span, comparison);
}
public static partial class EnumExtensions
{
public const char FlagSeparatorChar = ',';
public const string FlagSeparatorString = ", ";
public static bool TryGetEnumMemberOverridesToOriginals(Type enumType, [System.Diagnostics.CodeAnalysis.NotNullWhen(returnValue: true)] out Dictionary<ReadOnlyMemory<char>, string>? overridesToOriginals)
{
if (enumType == null)
throw new ArgumentNullException(nameof(enumType));
if (!enumType.IsEnum)
throw new ArgumentException(nameof(enumType));
overridesToOriginals = null;
foreach (var name in Enum.GetNames(enumType))
{
if (TryGetEnumAttribute<EnumMemberAttribute>(enumType, name, out var attr) && !string.IsNullOrWhiteSpace(attr.Value))
{
overridesToOriginals = overridesToOriginals ?? new(CharMemoryComparer.OrdinalIgnoreCase);
overridesToOriginals.Add(attr.Value.AsMemory(), name);
}
}
return overridesToOriginals != null;
}
public static bool TryGetEnumAttribute<TAttribute>(Type type, string name, [System.Diagnostics.CodeAnalysis.NotNullWhen(returnValue: true)] out TAttribute? attribute) where TAttribute : System.Attribute
{
var member = type.GetMember(name).SingleOrDefault();
attribute = member?.GetCustomAttribute<TAttribute>(false);
return attribute != null;
}
public static string? ReplaceRenamedEnumValuesToOriginals(string? value, bool isFlagged, Dictionary<ReadOnlyMemory<char>, string> overridesToOriginals)
{
if (string.IsNullOrWhiteSpace(value))
return value;
var trimmed = value.AsMemory().Trim();
if (overridesToOriginals.TryGetValue(trimmed, out var #override))
value = #override;
else if (isFlagged && trimmed.Length > 0)
{
var sb = new StringBuilder();
bool replaced = false;
foreach (var n in trimmed.Split(EnumExtensions.FlagSeparatorChar, StringSplitOptions.TrimEntries))
{
ReadOnlySpan<char> toAppend;
if (overridesToOriginals.TryGetValue(n, out var #thisOverride))
{
toAppend = thisOverride.AsSpan();
replaced = true;
}
else
toAppend = n.Span;
sb.Append(sb.Length == 0 ? null : EnumExtensions.FlagSeparatorString).Append(toAppend);
}
if (replaced)
value = sb.ToString();
}
return value;
}
}
public static class StringExtensions
{
public static IEnumerable<ReadOnlyMemory<char>> Split(this ReadOnlyMemory<char> chars, char separator, StringSplitOptions options = StringSplitOptions.None)
{
int index;
while ((index = chars.Span.IndexOf(separator)) >= 0)
{
var slice = chars.Slice(0, index);
if ((options & StringSplitOptions.TrimEntries) == StringSplitOptions.TrimEntries)
slice = slice.Trim();
if ((options & StringSplitOptions.RemoveEmptyEntries) == 0 || slice.Length > 0)
yield return slice;
chars = chars.Slice(index + 1);
}
if ((options & StringSplitOptions.TrimEntries) == StringSplitOptions.TrimEntries)
chars = chars.Trim();
if ((options & StringSplitOptions.RemoveEmptyEntries) == 0 || chars.Length > 0)
yield return chars;
}
}
Then add the binder in ConfigureServices() like so:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options =>
{
options.ModelBinderProviders.Insert(0, new EnumMemberEnumTypeModelBinderProvider(options));
});
}
Notes:
EnumTypeModelBinder and base class SimpleTypeModelBinder provide no useful extension points to customize the parsing of the incoming value string, thus it was necessary to copy some of their logic.
Precisely emulating the logic of SimpleTypeModelBinder is somewhat difficult because it supports both numeric and textual enum values -- including mixtures of both for flags enums. The binder above retains that capability, but at a cost of also allowing original enum names to be bound successfully. Thus the values on-hold and onhold will be bound to Status.OnHold.
Conversely, if you do not want to support binding of numeric values for enums, you could adapt the code of JsonEnumMemberStringEnumConverter from this answer to System.Text.Json: How do I specify a custom name for an enum value?. Demo fiddle here. This approach also avoids binding to the original, unrenamed enum names.
Matching of override names with original enum names is case-insensitive, so override names that differ only in case are not supported.

JsonConverterAttribute is not working for Deserialization in ASP.NET Core 3.1 / 5.0

I want set property names at runtime. I already achieve this for serialization.
For example. I have a simple model like as below:
[JsonConverter(typeof(DataModelConverter))]
public class DataModel
{
public string Name { get; set; }
public int Age { get; set; }
}
And I have a simple DataModelConverter, that inherited from JsonConverter:
public class DataModelConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Type type = value.GetType();
JObject jo = new JObject();
foreach (PropertyInfo prop in type.GetProperties())
{
jo.Add(prop.Name == "Name" ? "FullName" : prop.Name, new JValue(prop.GetValue(value)));
}
jo.WriteTo(writer);
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DataModel);
}
}
And I have a simple controller like as below:
[Route("api/[controller]")]
[ApiController]
public class NewtonController : ControllerBase
{
public IEnumerable<DataModel> GetNewtonDatas([FromBody] DataModel input)
{
return new List<DataModel>()
{
new DataModel
{
Name="Ramil",
Age=25
},
new DataModel
{
Name="Yusif",
Age=26
}
};
}
}
If I call this API, result will like as below (Showing FullName Instead of Name):
[
{
"FullName": "Ramil",
"Age": 25
},
{
"FullName": "Yusif",
"Age": 26
}
]
But I have a problem. This is not working for deserialization.
For example: If I call this API with this body, then Name will null.
{
"FullName":"Ramil"
}
My attribute is not working for deserialization. I want set property name via attribute for deserialization at runtime .
I don't want use some middleware, I want to achieve this only by using the any attribute at runtime. I must read JSON property names from my appsettings.json file.
Thanks for help!
You have overridden CanRead to return false:
public override bool CanRead
{
get { return false; }
}
This causes Json.NET not to call your your converter's DataModelConverter.ReadJson() method during deserialization, and instead use default deserialization. Since "FullName" does not have the same (case-invariant) name as the Name property, it never gets set, and remains null.
To fix this, remove the override for CanRead (the default implementation returns true) and implement ReadJson(), e.g. as follows:
public class DataModelConverter : NameRemappingConverterBase
{
static string AlternateName => "FullName";
static string OriginalName => "Name";
public override bool CanConvert(Type objectType) => objectType == typeof(DataModel);
// Replace the below logic with name mappings from appsettings.json
protected override string ToJsonPropertyName(JsonProperty property) =>
string.Equals(property.UnderlyingName, OriginalName, StringComparison.OrdinalIgnoreCase) ? AlternateName : base.ToJsonPropertyName(property);
protected override string FromJsonPropertyName(string name) =>
string.Equals(name, AlternateName, StringComparison.OrdinalIgnoreCase) ? OriginalName : base.FromJsonPropertyName(name);
}
public abstract class NameRemappingConverterBase : JsonConverter
{
protected virtual string ToJsonPropertyName(JsonProperty property) => property.PropertyName;
protected virtual string FromJsonPropertyName(string name) => name;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
return null;
if (reader.TokenType != JsonToken.StartObject)
throw new JsonSerializationException(string.Format("Unexpected token {0}", reader.TokenType));
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(objectType);
var value = existingValue ?? contract.DefaultCreator();
while (reader.ReadToContentAndAssert().TokenType != JsonToken.EndObject)
{
if (reader.TokenType != JsonToken.PropertyName)
throw new JsonSerializationException(string.Format("Unexpected token {0}", reader.TokenType));
var name = FromJsonPropertyName((string)reader.Value);
reader.ReadToContentAndAssert();
var property = contract.Properties.GetProperty(name, StringComparison.OrdinalIgnoreCase);
if (!ShouldDeserialize(property))
{
reader.Skip();
}
else
{
var propertyValue = serializer.Deserialize(reader, property.PropertyType);
property.ValueProvider.SetValue(value, propertyValue);
}
}
return value;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType());
writer.WriteStartObject();
foreach (var property in contract.Properties.Where(p => ShouldSerialize(p, value)))
{
var propertyValue = property.ValueProvider.GetValue(value);
if (propertyValue == null && serializer.NullValueHandling == NullValueHandling.Ignore)
continue;
var name = ToJsonPropertyName(property);
writer.WritePropertyName(name);
serializer.Serialize(writer, propertyValue);
}
writer.WriteEndObject();
}
protected virtual bool ShouldDeserialize(JsonProperty property) =>
property != null && property.Writable;
protected virtual bool ShouldSerialize(JsonProperty property, object value) =>
property.Readable && !property.Ignored && (property.ShouldSerialize == null || property.ShouldSerialize(value));
}
public static partial class JsonExtensions
{
public static JsonReader ReadToContentAndAssert(this JsonReader reader) =>
reader.ReadAndAssert().MoveToContentAndAssert();
public static JsonReader MoveToContentAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (reader.TokenType == JsonToken.None) // Skip past beginning of stream.
reader.ReadAndAssert();
while (reader.TokenType == JsonToken.Comment) // Skip past comments.
reader.ReadAndAssert();
return reader;
}
public static JsonReader ReadAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (!reader.Read())
throw new JsonReaderException("Unexpected end of JSON stream.");
return reader;
}
}
Demo fiddle here.

How to serialize top level objects with Json.NET as objects and nested objects as references

I want to be able to serialize an object fully if it is at the top level of the serialization context but serialize objects lower in the context by reference.
I've searched and tried tests with Custom Contract Resolvers, Custom Json Converters and custom IReferenceResolver but I can't find a way to do this.
For example, imagine an IdType class that at the top level I want to serialize with all its properties but where I come across a reference to such an object in a property or list or dictionary I want to produce a reference.
For this type and test
public class IdType
{
public IdType(string id)
{
Id = id;
}
public string Id {get;}
public string Name {get;set;}
public int Number {get; set;}
public IdType OtherType { get; set; }
public IEnumerable<IdType> Types { get; set;}
public IDictionary<IdType, string> { get; set; }
public IDictionary<string, IdType> {get; set; }
}
[TestMethod]
public void SerializeTest()
{
var t1 = new IdType(1) { Name = 'Alice', Number = 42 };
var t2 = new IdType(2) { Name = 'Bob', Number = 21, OtherType = t1 };
var t3 = new IdType(2) { Name = 'Charlie', Number = 84, OtherType = t2, Types = new[] {t1, t2} };
var testTypes = new[]
{
t1,
t3
};
var serializer = new JsonSerializer
{
Formatting = Formatting.Indented,
};
StringWriter writer;
using (writer = new StringWriter())
{
serializer.Serialize(writer, myObject);
}
Console.WriteLine(writer.ToString());
}
I want output like this
[
{
"Id": "1",
"Name": "Alice"
"Number": 42,
},
{
"Id": "3",
"Name": "Charlie"
"Number": 84,
"OtherType": 2
"Types": [
"Id" : 1, 2
]
}
]
A JsonConverter has no context so it'll either always convert one way or another.
A custom resolver (derived from DefaultContractResolver) will work for a property of type IdType but I can't work out how to make it work with lists and dictionaries.
Latterly I've tried using PreserveReferenceHandling and a custom IReferenceResolver that has the IDs of the top level elements. But this doesn't work because the serialization is depth first.
Any suggestions to achieve this would be gratefully received
I think I've answered my own question. If I use a combination of a custom contract resolver and a custom converter and conditionally add the converter to the properties I want to serialize to IDs then it seems to work.
I've not implemented dictionaries yet but this works for basic properties and lists:
public class CustomResolver : DefaultContractResolver
{
readonly CamelCasePropertyNamesContractResolver innerResolver = new CamelCasePropertyNamesContractResolver();
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var jsonProperty = base.CreateProperty(member, memberSerialization);
if (!jsonProperty.PropertyType.IsPrimitive && jsonProperty.PropertyType != typeof(string) && jsonProperty.Readable)
{
var innerContract = innerResolver.ResolveContract(jsonProperty.PropertyType);
if (typeof(IdType).IsAssignableFrom(innerContract.UnderlyingType) && innerContract is JsonObjectContract objectContract)
{
jsonProperty.Converter = new IdConverter();
}
else if (typeof(IEnumerable<IdType>).IsAssignableFrom(innerContract.UnderlyingType) && innerContract is JsonArrayContract arrayContract)
{
jsonProperty.Converter = new IdConverter();
}
}
return jsonProperty;
}
}
internal class IdConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (typeof(IdType).IsAssignableFrom(objectType) ||
typeof(IEnumerable<IdType>).IsAssignableFrom(objectType));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is IdType item)
{
JToken token = JToken.FromObject(item.Id);
token.WriteTo(writer);
}
else if (value is IEnumerable<IdType> itemCollection)
{
JArray array = new JArray();
foreach (var i in itemCollection)
{
JToken token = JToken.FromObject(i.Id);
array.Add(token);
}
array.WriteTo(writer);
}
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To serialize and making use of the custom resolver you would:
var serializer = new JsonSerializer
{
Formatting = Formatting.Indented,
ContractResolver = new CustomResolver(),
};

JSON.NET strongly typed object inheriting from DynamicObject deserialization/serialization

C#
Given:
[JsonObject(MemberSerialization.OptOut)]
public class Customer : DynamicObject{
public string FirstName { get; set; }
public string LastName { get; set; }
}
JavaScript:
var customer = {
FirstName: "John",
LastName: "Doe",
DOB: "12/18/1984"
};
Is there a a setting in JSON.NET or something else that has to happen such that the DOB would be deserialized to strongly typed Customer when json is posted to server?
to get this to work use custom converter overriding the ReadJson, and WriteJson methods
public class CustomConverter : JsonConverter{
public override void WriteJson(JsonWriter writer,
object value,
JsonSerializer serializer)
{
if (value is DynamicSword)
{
var ds = (DynamicSword)value;
string[] serializable;
string[] notSerializable;
ds.SetSerializableAndNotSerializable(out serializable, out notSerializable);
var jobject = new JObject();
foreach (var item in serializable)
{
var tempValue = ds[item];
if (tempValue != null)
{
jobject.Add(item, JToken.FromObject(tempValue));
}
}
jobject.WriteTo(writer);
}
else
{
JToken t = JToken.FromObject(value);
t.WriteTo(writer);
}
}
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
{
ConstructorInfo magicConstructor = objectType.GetConstructor(Type.EmptyTypes);
var newObject = magicConstructor.Invoke(new object[]{});
JObject jObject = JObject.Load(reader);
if (newObject is DynamicSword)
{
var ds = (DynamicSword)newObject;
hydrate(jObject, ds);
}
else
{
//do something different?
//really shoulnt be in here anyways
}
return newObject;
}
....
}

Custom non-primitive type in Entity Framework code-first 4.1

I have this custom type:
public struct PasswordString
{
private string value;
public PasswordString(string value)
{
this.value = MD5.CalculateMD5Hash(value);
}
public string Value
{
get { return this.value; }
set { this.value = MD5.CalculateMD5Hash(value); }
}
public static implicit operator PasswordString(string value)
{
return new PasswordString(value);
}
public static implicit operator string(PasswordString value)
{
return value.Value;
}
public static bool operator ==(string x, PasswordString y)
{
return x.CompareTo(y) == 0;
}
public static bool operator !=(string x, PasswordString y)
{
return x.CompareTo(y) != 0;
}
public override string ToString()
{
return Value;
}
}
public static class MD5
{
public static string CalculateMD5Hash(string input)
{
System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create();
byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
byte[] hash = md5.ComputeHash(inputBytes);
System.Text.StringBuilder sb = new System.Text.StringBuilder();
for (int i = 0; i < hash.Length; i++)
{
sb.Append(hash[i].ToString("X2"));
}
return sb.ToString();
}
}
So, I want tu use this type in my Entity Framework project. How can I map a type to work just like a string.
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public PasswordString Password { get; set; }
}
The using sample:
User user = new User()
{
Username = "steve",
Password = "apple"
};
System.Console.WriteLine(user.Password == "apple");
System.Console.WriteLine(user.Password);
This code produces:
True
1F3870BE274F6C49B3E31A0C6728957F
My goal, is to query against the Entity Framework to have some like this:
var q = from u in users
where u.Username == "steve" && u.Password == "apple"
orderby u.Username
select u;
So then, I never need to encrypt the password, but it will be stored encrypted on the database.
I am trying to use this class with EF, but with no success. There is a way achieve this with Entity Framework 4.1?
Entity framework doesn't support type converters or any other way to map simple database column to your custom type and such feature is even not yet planned for the next release. So the answer to your question is: not possible.
It's not possible to use your class (or struct) as a complex type in a LINQ to Entities query. I order to execute this comparison ...
u.Password == "apple"
... the constructor must be called which calls in turn MD5.CalculateMD5Hash. This method cannot be translated into SQL and the query will throw an exception. It's likely that LINQ to Entities even doesn't support any overloaded operator (like ==) - for the same reason: EF can't translate it into SQL.