How to Serialize SqlHierarchyId to String - c#

I have a table with a SqlHierarchyId field type.
The default JSON serialization returns a javascript object that looks like this: {isNull: false} instead of a string like this /1/
Is there some way to set the serialization properly?
public class MyClass
{
[SerializeToString]
public SqlHierarchyId NodeId { get; set; }
}

According to the MSDN documentation, SqlHierarchyId implements both a ToString() method and a static Parse() method to facilitate converting it to a canonical string representation and back. So, assuming you are using Json.Net for serialization, you should be able to make a straightforward JsonConverter class to bridge the gap. Here is what it would look like:
public class SqlHierarchyIdConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(SqlHierarchyId));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
string id = (string)reader.Value;
return (id == null || id == SqlHierarchyId.Null.ToString()) ? SqlHierarchyId.Null : SqlHierarchyId.Parse(id);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
}
To use it, you can either mark the SqlHierarchyId properties in your classes with a [JsonConverter] attribute like this:
class MyClass
{
[JsonConverter(typeof(SqlHierarchyIdConverter))]
public SqlHierarchyId NodeId { get; set; }
}
Or, alternatively, you can pass an instance of the converter as a parameter to SerializeObject or DeserializeObject:
string json = JsonConvert.SerializeObject(myClass, new SqlHierarchyIdConverter());
Here is a round-trip demo: https://dotnetfiddle.net/7h7E82

Related

How can I customize the deserialization of a nested property in Json.net without adding attributes

Suppose I have these classes:
public class Bar
{
public Foo MyFoo { get; set; }
}
public class Foo
{
public string[] Stuff { get; set; }
}
And I have this JSON structure:
{
"MyFoo":
{
"Stuff":"those,are,my,stuff"
}
}
And I have a code path where a JObject is being converted to Bar using code like below:
myJObject.ToObject(typeof(Bar))
Now what I need to do is to supply the ToObject with a custom serializer to convert the string property Stuff into an array of string (using string.Split(...).ToArray())
I was asked not to add attributes on the client class 'Bar' so after looking around it seemed like a ContractResolver is in order but the issue is that the resolver only lets me handle direct properties of the root Type, that is Bar in my example, and I can't register a JsonConverter on a nested property.
So my question to you guys is, is this even achievable using Json.net?
Note that I need to do this not only for the Bar class but to an unlimited amount of classes with unknown structure so I can't hard-code a solution that will work for one type of class.
Based on your description, I don't see why you would need a ContractResolver. You know that the properties you want to deserialize specially will always be of type string[], so just make a converter that handles that type. Maybe something like this:
public class CsvStringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string[]);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Null)
return null;
if (token.Type == JTokenType.String)
return ((string)token).Split(',');
if (token.Type == JTokenType.Array)
return token.ToObject<string[]>(serializer);
throw new JsonException("Unexpected token type: " + token.Type);
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then, to use it with your JObject, create a new JsonSerializer instance, add the converter to it, and pass the serializer to the ToObject() method like this:
var serializer = new JsonSerializer();
serializer.Converters.Add(new CsvStringConverter());
var bar = myJObject.ToObject<Bar>(serializer);
Working demo here: https://dotnetfiddle.net/qmeBoh

Customizing response serialization in ASP.NET Core MVC

Is it possible to customize the way types are serialized to the response in ASP.NET Core MVC?
In my particular use case I've got a struct, AccountId, that simply wraps around a Guid:
public readonly struct AccountId
{
public Guid Value { get; }
// ...
}
When I return it from an action method, unsurprisingly, it serializes to the following:
{ "value": "F6556C1D-1E8A-4D25-AB06-E8E244067D04" }
Instead, I'd like to automatically unwrap the Value so it serializes to a plain string:
"F6556C1D-1E8A-4D25-AB06-E8E244067D04"
Can MVC be configured to achieve this?
You can customize the output produced by JSON.NET with a custom converter.
In your case, it would look like this:
[JsonConverter(typeof(AccountIdConverter))]
public readonly struct AccountId
{
public Guid Value { get; }
// ...
}
public class AccountIdConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
=> objectType == typeof(AccountId);
// this converter is only used for serialization, not to deserialize
public override bool CanRead => false;
// implement this if you need to read the string representation to create an AccountId
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
=> throw new NotImplementedException();
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (!(value is AccountId accountId))
throw new JsonSerializationException("Expected AccountId object value.");
// custom response
writer.WriteValue(accountId.Value);
}
}
If you prefer not to use the JsonConverter attribute, it's possible to add converters in ConfigureServices (requires Microsoft.AspNetCore.Mvc.Formatters.Json):
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc()
.AddJsonOptions(options => {
options.SerializerSettings.Converters.Add(new AccountIdConverter());
});
}

How to serialize a Guid with C# without creating an object?

I want my Guids to be serialized into a short form of Guid (as seen here). For full compatibility, I want the ShortGuid class to serialize into the short form, and not the long form.
I've tried using custom serialization, but I can't seem to make the whole object serialize into the short string, only into an object which contains the string. This is what I've tried:
[Serializable]
public class ShortGuid : ISerializable
{
public Guid Guid { get; }
protected ShortGuid(SerializationInfo info, StreamingContext context)
{
Guid = Decode(info.GetString("Guid"));
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Guid", Encode(Guid));
}
}
Which works, but if I serialize it:
var guid = new ShortGuid(Guid.NewGuid());
var str = JsonConvert.Serialize(guid);
The serialized string I get looks like this:
{ Guid: "xxxxxxxxxxxxxxxxxx" }
While the serialized string I WANT is just
xxxxxxxxxxxxxxxxxx
I've turned everything around, but can't get this to work. How can this be done?
NOTE: I don't want this to work only on JsonConvert, this is only an example. I would like the class the always be serialized correctly.
this work perfectly for me:
public class GuidShortGuidConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Guid);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var shortGuid = new ShortGuid(reader.Value.ToString());
return shortGuid.Guid;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var customValue = new ShortGuid((Guid) value);
writer.WriteValue(customValue.ToString());
}
}
What you want is not about serialization but implicit casting.
Serialization works on structured key/value base depending on serialization type.
With implicit string casting
public static implicit operator string(ShortGuid shortGuid)
{
return Encode(sh.Guid);
}
You can use
var guid = new ShortGuid(Guid.NewGuid());
string str = guid;

Convert specific objects to dictionaries before serialization

I'm using Json.NET to serialize validation data for data field. On the .NET side, validation data is a list of ValidationAttribute objects. However, I'd like to serialize them in a special form like this:
[
{ Type: 'Required', ErrorMessage: '{FieldName} is required' },
{ Type: 'RegularExpression', Pattern: '^\d+$', ErrorMessage: '...'
]
In an ideal solution I could simply intercept the object before serialization and, I could create a corresponding Dictionary<string, object> object to serialize instead of the original one.
Are there any solutions for this scenario?
You can implement your own JsonConverter class and convert your collection as you wish.
You just need to create you class and inherit it from JsonConverter
public class YourSerializer : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return typeof(YourClassName).IsAssignableFrom(objectType);
}
}
and then you need to decorate your class which will be serialized with the attribute (looks like it's not what you want)
[JsonConverter(typeof(YourSerializer))]
public class YourClassName
{
public string Name { get; set; }
public string Value { get; set; }
}
or, pass an instance of your serializer to Serialize methos:
string json = JsonConvert.SerializeObject(sourceObj, Formatting.Indented, new YourSerializer(typeof(yourClassName)));
Here is a few links:
http://www.newtonsoft.com/json/help/html/CustomJsonConverter.htm
http://blog.maskalik.com/asp-net/json-net-implement-custom-serialization/
Hope, it will help.

Json.NET deserializing Mongo ObjectId is giving the wrong result

I'm using the official Mongo C# Driver, and RestSharp to call a Rest Api with Json.NET to perform my serialization/deserialization. Say I have a Person class as follows, which I'd like to POST & GET:
public class Person
{
[JsonProperty("_id"),JsonConverter(typeof(ObjectIdConverter))]
public ObjectId Id {get;set;}
public string Name {get;set;}
}
I create a new Person object:
var person = new Person{Id = ObjectId.GenerateId(),Name='Joe Bloggs'};
POST it, and on the server I see the following which is correct:
{ _id: 52498b56904ee108c99fbe88, name: 'Joe Bloggs'}
The problem, is when I perform a GET the ObjectId I get on the client is {0000000000000...}
i.e. not the {5249.....} I'd expect. The raw response is showing the correct value, but once I deserialize I loose it.
The ObjectIdConverter code is :
public class ObjectIdConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value.ToString());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var objectId = (ObjectId)existingValue; // at this point existingValue is {000...}
return objectId;
}
public override bool CanConvert(Type objectType)
{
return (objectType == typeof (ObjectId));
}
}
Any help would be appreciated.
You are implementing the ReadJson method of the converter incorrectly. The existingValue parameter does not give you the deserialized value read from the JSON, it gives you the existing value of the object that you will be replacing. In most cases this will be null or empty. What you need to do is use the reader to get the value from the JSON, convert it as needed, then return the converted value.
Assuming your ObjectId class has a constructor that accepts a hex string, here is how you would implement the ReadJson method:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
return new ObjectId(token.ToObject<string>());
}

Categories