YamlDotNet - Custom Serialization - c#

I have a .NET class which represents a RPC method call, like this:
class MethodCall
{
public string MethodName { get; set; }
public Collection<object> Arguments { get; set; }
}
I want to serialize a Collection<MethodCall> to YAML. I'm using YamlDotNet to achieve this.
By default, YamlDotNet will serialize these objects like this:
methodName: someName
arguments:
- arg1
- arg2
- ...
I would like to simplify the resulting YAML to:
someName:
- arg1
- arg2
Is there any easy way to achieve this? Please note that the arguments can be complex objects (i.e. not simple scalars).

You can achieve this by registering an implementation of IYamlTypeConverter that performs the conversion that you need.
Here's a possible implementation:
public sealed class MethodCallConverter : IYamlTypeConverter
{
// Unfortunately the API does not provide those in the ReadYaml and WriteYaml
// methods, so we are forced to set them after creation.
public IValueSerializer ValueSerializer { get; set; }
public IValueDeserializer ValueDeserializer { get; set; }
public bool Accepts(Type type) => type == typeof(MethodCall);
public object ReadYaml(IParser parser, Type type)
{
parser.Consume<MappingStart>();
var call = new MethodCall
{
MethodName = (string)ValueDeserializer.DeserializeValue(parser, typeof(string), new SerializerState(), ValueDeserializer),
Arguments = (Collection<object>)ValueDeserializer.DeserializeValue(parser, typeof(Collection<object>), new SerializerState(), ValueDeserializer),
};
parser.Consume<MappingEnd>();
return call;
}
public void WriteYaml(IEmitter emitter, object value, Type type)
{
emitter.Emit(new MappingStart());
var call = (MethodCall)value;
ValueSerializer.SerializeValue(emitter, call.MethodName, typeof(string));
ValueSerializer.SerializeValue(emitter, call.Arguments, typeof(Collection<object>));
emitter.Emit(new MappingEnd());
}
}
The converter needs to be registered into the SerializerBuilder and DeserializerBuilder through the WithTypeConverter method. Note that YamlDotNet does not provide us with a way to call the (de)serializer recursively, so we have to set some public properties as a workaround. This is not as clean as it could be, but still works:
string SerializeMethodCall(MethodCall call)
{
var methodCallConverter = new MethodCallConverter();
var serializerBuilder = new SerializerBuilder()
.WithNamingConvention(CamelCaseNamingConvention.Instance)
.WithTypeConverter(methodCallConverter);
methodCallConverter.ValueSerializer = serializerBuilder.BuildValueSerializer();
var serializer = serializerBuilder.Build();
var yaml = serializer.Serialize(call);
return yaml;
}
MethodCall DeserializeMethodCall(string yaml)
{
var methodCallConverter = new MethodCallConverter();
var deserializerBuilder = new DeserializerBuilder()
.WithNamingConvention(CamelCaseNamingConvention.Instance)
.WithTypeConverter(methodCallConverter);
methodCallConverter.ValueDeserializer = deserializerBuilder.BuildValueDeserializer();
var deserializer = deserializerBuilder.Build();
var call = deserializer.Deserialize<MethodCall>(yaml);
return call;
}

Related

Serialize custom type to JSON

class Program
{
static void Main(string[] args)
{
JSONClass jsonClass = new JSONClass();
JSONElement el = new JSONElement
{
A = 5,
B = "test1"
};
JSONElement el2 = new JSONElement
{
A = 3,
B = "test2"
};
jsonClass.JSONList.Add(el);
jsonClass.JSONList.Add(el2);
var output = JsonSerializer.Serialize<JSONClass>(jsonClass);
Console.WriteLine(output);
}
}
public class JSONClass
{
public List<JSONElement> JSONList = new List<JSONElement>();
}
public class JSONElement
{
public int A { get; set; }
public string B { get; set; }
}
This code returns {} which means that JsonSerializer.Serialize failed to do what it supposed to do. I imagine its because its not smart enough to handle custom types. And here is my question, how to do it. Internet is full of articles how to write custom converters etc, but none of them mention custom types.
Your JSONList member is a public field - whereas JsonSerializer looks for properties.
Change your code for JSONClass to this:
public class JSONClass
{
public List<JSONElement> JSONList { get; } = new List<JSONElement>();
}
The output is then:
{"JSONList":[{"A":5,"B":"test1"},{"A":3,"B":"test2"}]}
The bigger lesson to learn here is not to assume that the mistake is in the library you're using. Always start with an expectation that the problem is in your own code. Sometimes you'll find it really is in the library or system code (or in the compiler etc) but in my experience that's relatively rare.

Detecting mismatch between constructor parameter names and property names with immutable objects

Consider the following mutable object:
class SomePoco
{
public int Id{get;set;}
public string Name{get;set;}
}
Let's round trip it through Json.NET:
var p=new SomePoco{Id=4,Name="spender"};
var json=JsonConvert.SerializeObject(p);
var pr = JsonConvert.DeserializeObject<SomePoco>(json);
Console.WriteLine($"Id:{pr.Id}, Name:{pr.Name}");
All is good.
Now, let's make out POCO immutable and feed values via a constructor:
class SomeImmutablePoco
{
public SomeImmutablePoco(int id, string name)
{
Id = id;
Name = name;
}
public int Id{get;}
public string Name{get;}
}
... and round-trip the data again:
var p = new SomeImmutablePoco(5, "spender's immutable friend");
var json = JsonConvert.SerializeObject(p);
var pr = JsonConvert.DeserializeObject<SomeImmutablePoco>(json);
Console.WriteLine($"Id:{pr.Id}, Name:{pr.Name}");
Still good.
Now, let's make a small change to our immutable class by renaming a constructor parameter:
class SomeImmutablePoco
{
public SomeImmutablePoco(int pocoId, string name)
{
Id = pocoId;
Name = name;
}
public int Id{get;}
public string Name{get;}
}
then:
var p = new SomeImmutablePoco(666, "diabolo");
var json = JsonConvert.SerializeObject(p);
var pr = JsonConvert.DeserializeObject<SomeImmutablePoco>(json);
Console.WriteLine($"Id:{pr.Id}, Name:{pr.Name}");
Oh dear... It looks like Json.NET is doing some reflective magic over the names of our constructor parameters and matching them to property names in our POCO/json. This means that our freshly deserialized object doesn't get an Id assigned to it. When we print out the Id, it's 0.
This is bad, and particularly troublesome to track down.
This problem might exist in a large collection of POCOs. How can I automate finding these problem POCO classes?
Here is the code that finds such classes using reflection:
var types = new List<Type>() { typeof(SomeImmutablePoco) }; // get all types using reflection
foreach (var type in types)
{
var props = type.GetProperties(bindingAttr: System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
foreach (var ctor in type.GetConstructors())
{
foreach (var param in ctor.GetParameters())
{
if (!props.Select(prop => prop.Name.ToLower()).Contains(param.Name.ToLower()))
{
Console.WriteLine($"The type {type.FullName} may have problems with Deserialization");
}
}
}
}
You can map your json property like that:
class SomeImmutablePoco
{
public SomeImmutablePoco(int pocoId, string name)
{
Id = pocoId;
Name = name;
}
[JsonProperty("pocoId")]
public int Id { get; }
public string Name { get; }
}

How to access a private constructor from controller to model class in mvc 4

Is there any way to access a private constructor from controller to model?
The controller containing method calling the model is as follows:
public ActionResult ReadXML()
{
XmlSerializer reader = new XmlSerializer(typeof(List<Asseted>));
TextReader textReader = new StreamReader(#"D:\Tial2.xml");
List<Asseted> asseted;
List<Asseted> list = new List<Asseted>();
asseted = (List<Asseted>)reader.Deserialize(textReader);
textReader.Close();
for (int i = 0; i < asseted.Count; i++)
{
string data123 = Convert.ToString(asseted[i].PopertyValue);
string data234 = Convert.ToString(asseted[i].PropertyName);
list.Add(new Asseted(data123,data234));
}
return View();
}
The model containing the Method to be called is as follows:
[XmlRoot]
public class Asseted
{
string pName, pValue;
private string data234;
private string data123;
private Asseted(string data234, string data123)
{
// TODO: Complete member initialization
PropertyName = data234;
PopertyValue = data123;
}
[XmlElement]
public string PropertyName { get; set; }
[XmlElement]
public string PopertyValue { get; set; }
}
Is there any way to access a private constructor from controller to model?
While it may be arguable if this is the right way to solve your problem, the question itself can be answered with YES. And it's not even very hard with reflection.
ConstructorInfo constructor = typeof(Asseted).GetConstructor(
BindingFlags.NonPublic | BindingFlags.Instance,
null,
new[] { typeof(string), typeof(string) },
null);
Asseted instance = constructor.Invoke(new[] {
"data234",
"data123"
}) as Asseted;
Basically you get the type (Asseted), get the constructor that matches your known parameter types and invoke it. Done.
Additional Note:
Depending on your compiler and actual code, you might run into a TypeAccessException.

Deserializing Json array with variable names first using C# Json.NET

I'm getting an irregular JSON array from the Census Bureau's public api.
The variable names are all in the first element, and I'm having trouble deserializing it.
http://api.census.gov/data/2014/pep/agesex?get=AGE,POP,SEX&for=us:*&DATE=7
gives me JSON like this:
[["AGE","POP","SEX","DATE","us"],
["0","3948350","0","7","1"],
["1","3962123","0","7","1"],
["2","3957772","0","7","1"],
["3","4005190","0","7","1"],
["4","4003448","0","7","1"],
["5","4004858","0","7","1"],
["6","4134352","0","7","1"],
["7","4154000","0","7","1"]]
I can successfully deserialize this using:
var test1 = JsonConvert.DeserializeObject<String[][]>(jsonStr);
However, I'm trying to deserialize it to a class like this:
public class TestClass
{
public string AGE { get; set; }
public string POP { get; set; }
public string SEX { get; set; }
public string DATE { get; set; }
public string us { get; set; }
}
I'm trying to do this:
var test2 = JsonConvert.DeserializeObject<TestClass[]>(jsonStr);
But I'm getting the following exception:
An exception of type 'Newtonsoft.Json.JsonSerializationException'
occurred in Newtonsoft.Json.dll but was not handled in user code
Additional information: Cannot create and populate list type
TestClass. Path '[0]', line 1, position
2.
There's two parts to this.
First is turning the JSON in to data usable in C#, and the second is turning that data in to nice objects.
Here's a working dotNetFiddle.net example of the following code: https://dotnetfiddle.net/Cr0aRL
Each row in your JSON is made up of an array of strings.
So that's an array of an array of strings.
In C# that can be written as string[][].
So to turn the JSON in to usable data with JSON.Net you can do:
var json = "[[\"AGE\",\"POP\",\"SEX\",\"DATE\",\"us\"],[\"0\",\"3948350\",\"0\",\"7\",\"1\"],[\"1\",\"3962123\",\"0\",\"7\",\"1\"],[\"2\",\"3957772\",\"0\",\"7\",\"1\"],[\"3\",\"4005190\",\"0\",\"7\",\"1\"],[\"4\",\"4003448\",\"0\",\"7\",\"1\"],[\"5\",\"4004858\",\"0\",\"7\",\"1\"],[\"6\",\"4134352\",\"0\",\"7\",\"1\"],[\"7\",\"4154000\",\"0\",\"7\",\"1\"]]";
var rawData = JsonConvert.DeserializeObject<string[][]>(json);
Next up is is turning that data in to objects.
The first row is the header, containing the column names, so we want to grab that, and then figure out the column index for each column name.
var headerRow = rawData.First();
var ageIndex = Array.IndexOf(headerRow, "AGE");
var popIndex = Array.IndexOf(headerRow, "POP");
var sexIndex = Array.IndexOf(headerRow, "SEX");
var dateIndex = Array.IndexOf(headerRow, "DATE");
var usIndex = Array.IndexOf(headerRow, "us");
Now we have the indexes, we need to take each row, and convert it in to the appropriate object. I've used LINQ for this as it's very good at representing data processing in a clear way.
var testData = rawData
.Skip(1) //The first row is a header, not data
.Select(dataRow => new TestClass()
{
AGE = dataRow[ageIndex],
POP = dataRow[popIndex],
SEX = dataRow[sexIndex],
DATE = dataRow[dateIndex],
us = dataRow[usIndex]
});
Finally a bit of testing, to make sure you have the data you're expecting.
//Get the second data row as an example
var example = testData.Skip(1).First();
//Output example POP to check value
Console.WriteLine(example.POP);
Everything above is very manual.
You have to know what headers you expect, then you manually find the indexes, then you manually map the rows to objects.
It's quite possible for a simple use case that doing that is fine. But in larger and/or more complex systems you might want/need to automate those steps.
Automating those steps is possible, but is beyond the scope of this answer as how you approach it can depend on a lot of different factors.
You could make a custom JsonConverter to handle this conversion during deserialization. The conversion code is really not much different than other answers here, except that it is encapsulated into a separate class so that you don't muddy up your main code with the conversion details. From the point of view of your main code it "just works".
Here is how to write the converter:
public class TestClassArrayConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(TestClass[]));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JArray table = JArray.Load(reader);
TestClass[] items = new TestClass[table.Count - 1];
for (int i = 1; i < table.Count; i++)
{
JArray row = (JArray)table[i];
items[i - 1] = new TestClass
{
AGE = (string)row[0],
POP = (string)row[1],
SEX = (string)row[2],
DATE = (string)row[3],
us = (string)row[4]
};
}
return items;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
And here is how you would use it:
var test2 = JsonConvert.DeserializeObject<TestClass[]>(jsonStr, new TestClassArrayConverter());
Fiddle: https://dotnetfiddle.net/68Q0KT
You have to do the processing on your own, as there is no way the json deserializer can know, how to put the values into the respecitve variables.
If you know, this will be exactly this structure, you could for instance add an appropriate constructor
public TestClass(string[] values) {
AGE = values[0];
...
}
to your class. Then serialize your result to array of arrays of string and then pass the inner arrays to your constructor.
var t1 = JsonConvert.DeserializeObject<string[][]>(jsonStr);
//skip the first entry, as this contains the headers
var t2 = t1.Skip(1).Select(x=> new TestClass(x));
If your structure varies, you'll have to write some more complicated mapping code.
You will have to do some custom mapping as your Json does not have any naming conventions so you will have to work with the data in array and index formats. This will work:
var jsonStr = "[[\"AGE\",\"POP\",\"SEX\",\"DATE\",\"us\"], [\"0\",\"3948350\",\"0\",\"7\",\"1\"], [\"1\",\"3962123\",\"0\",\"7\",\"1\"], [\"2\",\"3957772\",\"0\",\"7\",\"1\"], [\"3\",\"4005190\",\"0\",\"7\",\"1\"], [\"4\",\"4003448\",\"0\",\"7\",\"1\"], [\"5\",\"4004858\",\"0\",\"7\",\"1\"], [\"6\",\"4134352\",\"0\",\"7\",\"1\"], [\"7\",\"4154000\",\"0\",\"7\",\"1\"]]";
var test2 = JsonConvert.DeserializeObject<string[][]>(jsonStr);
var test3 = test2.Select(x => new TestClass()
{
AGE = x[0].ToString(),
POP = x[1].ToString(),
SEX = x[2].ToString(),
DATE = x[3].ToString(),
us = x[4].ToString()
}).ToList();
//test Case
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
namespace ApiController.Test
{
[TestClass]
public class DownloadIrregularJsonStringObjects
{
string ApiKey => "YourPersonalCensusKey";
/// <summary>
/// You have to get your own ApiKey from the Census Website
/// </summary>
[TestMethod]
public void TestGetItem()
{
string url = $"http://api.census.gov/data/timeseries/healthins/sahie?get=NIC_PT,NAME,NUI_PT&for=county:*&in=state:*&time=2015&key={YourPersonalCensusKey}";
string expected = "Autauga County, AL";
IList<HealthData> actual = ApiController.DownloadIrregularJsonStringObjects.GetCensusHealthData(url);
Assert.AreEqual(actual[0].NAME, expected);
}
}
}
///Actual Assembly
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
namespace ApiController
{
public class DownloadIrregularJsonStringObjects
{
public static IList<HealthData> GetCensusHealthData(string url)
{
var json = GetData(url);
var rawData = JsonConvert.DeserializeObject<string[][]>(json);
var headerRow = rawData.First();
var nic_pt_Index = Array.IndexOf(headerRow, "NIC_PT");
var name_Index = Array.IndexOf(headerRow, "NAME");
var nui_pt_Index = Array.IndexOf(headerRow, "NUI_PT");
IList<HealthData> retVal = new List<HealthData>();
foreach (var r in rawData.Skip(1))
{
HealthData dataRow = new HealthData();
dataRow.NIC_PT = r[nic_pt_Index];
dataRow.NAME = r[name_Index];
dataRow.NUI_PT = r[nui_pt_Index];
retVal.Add(dataRow);
}
return retVal;
}
private static string GetData(string url)
{
using (var w = new WebClient())
{
var jsonData = string.Empty;
jsonData = w.DownloadString(url);
return jsonData;
}
}
}
public class HealthData
{
public string NIC_PT { get; set; }
public string NAME { get; set; }
public string NUI_PT { get; set; }
}
}

When to use SemanticModel.GetSymbolInfo and when SemanticModel.GetDeclaredSymbol

In some cases, when I'm trying to get the the ISymbol for my syntax node, I'm fail (getting null) when using SemanticModel.GetSymbolInfo but succeed when using SemanticModel.GetDeclaredSymbol.
I've attached an example bellow.
So my question is when to use each one of the methods for getting the semantic model?
public class Class1
{
public System.String MyString { get; set; }
public static void Main()
{
var str =
#"
namespace ClassLibrary31
{
public class Class1
{
public System.String MyString { get; set; }
}
}";
var syntaxTree = SyntaxFactory.ParseSyntaxTree(str);
MetadataReference[] metadataReferenceReferences = new MetadataReference[]
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
};
var compilation =
CSharpCompilation
.Create("TraceFluent",
new[] {syntaxTree},
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, warningLevel:1),
references: metadataReferenceReferences
);
var temp = compilation.Emit("temp");
var semanticModel = compilation.GetSemanticModel(syntaxTree, true);
PropertyDeclarationSyntax propertySyntaxNode =
syntaxTree.GetRoot()
.DescendantNodes()
.OfType<PropertyDeclarationSyntax>()
.First();
//var symbolInfo = semanticModel.GetDeclaredSymbol(propertySyntaxNode);
var symbol = semanticModel.GetDeclaredSymbol(propertySyntaxNode) as IPropertySymbol;
var typeInfo = semanticModel.GetTypeInfo(propertySyntaxNode).Type;
}
}
I believe you mean getting the symbol for a given syntax node, and not getting the semantic model for the tree.
Generally, when you want to get the underlying symbol of a declaration (class, property, method, ...), then you should use the GetDeclaredSymbol. Internally, GetSymbolInfo calls this method. You can see the different cases handled there. Declarations are not handled, so for those you'd need to use GetDeclaredSymbol, whose internals you can find here.

Categories