System.Text.Json deserializing where there is two possible property names - c#

Is it possible to deserialize a JSON into a class where a property name could be one of two values?
For example both of these JSONs would deserialize into the same property:
var json1 = "{\"A\":5}";
var json2 = "{\"B\":5}";
I thought that maybe I could use the JsonPropertyName attribute twice.
[JsonPropertyName("A")]
[JsonPropertyName("B")]
public int SomeInt { get; set; }
This is not allowed.
Then I thought maybe if the deserializer could not find the JsonPropertyName attribute it would look for the actual property name.
[JsonPropertyName("A")]
public int B { get; set; }
But that doesn't work either.

You could have two separate properties, with one delegating to another:
[JsonPropertyName("A")]
[Obsolete("Explain why here")]
public int A
{
get => B;
set => B = value;
}
[JsonPropertyName("B")]
public int B { get; set; }
The downside is that both will be written when serializing. I'd originally thought you could use JsonIgnoreAttribute for the obsolete property, but that would prevent it from being deserialized too :( Not so bad if you're only using this for parsing, but unpleasant if you're serializing too.
Additionally, if you ever parse a JSON file with both of them, whichever one is set last will win, which may not be ideal.

Related

Why does the JSON2CSharp online converter decorate each property with a [JsonProperty("...")] attribute?

I have this JSON response from an API:
{
"arguments": {
"Configuration": {
"Building_Configuration.Parameters_SP.fixtureStrategy_SP": "ETA",
"Building_Configuration.Parameters_SP.dimensionSelection_SP": "Imperial",
"Building_Configuration.Parameters_SP.controllerRobotic_SP": false,
"Building_Configuration.Parameters_SP.controllerBACNet_SP": false
}
}
}
I have this Root.cs Model file that I used the JSON to C# Converter to make that corresponds to the JSON above in my solution in C# Visual Studio 2019:
public class Root
{
public Arguments arguments { get; set; }
}
public class Arguments
{
public Configuration Configuration { get; set; }
}
public class Configuration
{
[JsonProperty("Building_Configuration.Parameters_SP.fixtureStrategy_SP")]
public string BuildingConfigurationParametersSPFixtureStrategySP { get; set; }
[JsonProperty("Building_Configuration.Parameters_SP.dimensionSelection_SP")]
public string BuildingConfigurationParametersSPDimensionSelectionSP { get; set; }
[JsonProperty("Building_Configuration.Parameters_SP.controllerRobotic_SP")]
public bool BuildingConfigurationParametersSPControllerRoboticSP { get; set; }
[JsonProperty("Building_Configuration.Parameters_SP.controllerBACNet_SP")]
public bool BuildingConfigurationParametersSPControllerBACNetSP { get; set; }
}
I'm trying to access and return the value of BuildingConfigurationParametersSPFixtureStrategySP (the first property in the Configuration class above) in this manner:
public string CreateExecPost()
{
/*...everthing here works fine down through the end of this method. I can set
breakpoints and step through the following code and look at the values of all the
following variables and all is well
....*/
var payload = new StringContent(newPost, Encoding.UTF8, "application/json");
var result = client.PostAsync(endpoint, payload).Result.Content.ReadAsStringAsync().Result;
Root MyObject = JsonConvert.DeserializeObject<Root>(result);
return MyObject.arguments.configuration.BuildingConfigurationParametersSPFixtureStrategySP;
} //The correct value for BuildingConfigurationParametersSPFixtureStrategySP is returned and all is well!
So, the question is why does the converter generate an attribute like...
[JsonProperty("Building_Configuration.Parameters_SP.fixtureStrategy_SP")]
...above each of the { get; set; } statements? I've researched and read about JsonProperty and JsonPropertyAttribute, but I'm still not fully clear on it. I'm not seeing what the tool uses to signal it to generate the attribute or why it does it.
This tool generates code with the Json.net library by default, and the official documentation doesn’t explain much on this class: https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_Serialization_JsonProperty.htm
There are other similar questions on how to use this, for example:
What [JsonProperty] used for in c#?
The general usage of this class is when you want to, or need to, rename a property. And the last one is relevant here.
The json parser normally tries to match the property names 1:1 with your class properties.
But whenever the json property name contains reserved keywords, language syntax, or otherwise illegal property names, you need to rename it.
In your example the name Building_Configuration.Parameters_SP.fixtureStrategy_SP contains periods. If you would try to name your get;set property like this, the code would not compile.
The site that generates the code knows this, and will add the required JsonProperty to map the full json property name to the class field that was renamed to BuildingConfigurationParametersSPFixtureStrategySP (the illegal characters were removed)
See for valid property names: https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/identifier-names
And for reference: Accessing properties with a dot in their name

How can I deserialize this specific json string?

I am trying to deserialize the following json string using Newtonsoft Json. I am able to get the string successfully, but when I try using JsonConvert.DeserializeObject<ServerList>(response, settings);, the try catch fails.
[
{"endpoint":"127.0.0.1","id":6,"identifiers":["steam:","license:","xbl:","live:","discord:"],"name":"Blurr","ping":160},
{"endpoint":"127.0.0.1","id":7,"identifiers":["steam:","license:","xbl:","live:","discord:"],"name":"Knight","ping":120}
]
I believe my issue is because the players array being unnamed.
I have tried [JsonProperty("")] and [JsonProperty] for the Users var, I have also tried using List and Array instead of IList.
Here is the object. This may be completely wrong, I have tried many ways of doing this.
public class ServerList
{
// I have tried many ways of doing this array/list. This is just the latest way I tried.
[JsonProperty]
public static IList<Player> Users { get; set; }
}
public class Player
{
[JsonProperty("endpoint")]
public static string Endpoint { get; set; }
[JsonProperty("id")]
public static string ServerId { get; set; }
[JsonProperty("identifiers")]
public static IList<string> Identifiers { get; set; }
[JsonProperty("name")]
public static string Name { get; set; }
[JsonProperty("ping")]
public static int Ping { get; set; }
}
I am expecting to get a 'ServerList' object returned with a list of all the players connected to the server.
Ask any questions you need to, I don't often work with json in this format. Thank you in advance!
ERROR: Error reading JObject from JsonReader. Current JsonReader item is not an object: StartArray. Path '', line 1, position 1.
Simplest way: Your json is an array, so deserialize to an array:
JsonConvert.DeserializeObject<Player[]>(response,settings);
As noted in the comments, properties on the Player should not be static.
If you insist on an object structure similar to what you posted, an object with a Usersproperty, even though it's not present in the JSON, that is also possible, by implementing a custom JSON converter. There's an article with an example of that here: https://www.jerriepelser.com/blog/custom-converters-in-json-net-case-study-1/
HOWEVER; I would recommend sticking to the types present in the json, and perhaps later construct the object that makes sense to the model in your program. This way, what you deserialize are true to the json you are getting, but make no sacrifices on the model you'd like:
var players = JsonConvert.DeserializeObject<Player[]>(response,settings);
var serverList = new ServerList {Users = players};

Auto-generate C# classes from JSON, including property initializers

There are a number of great ways to auto-generate C# code from JSON, such as here and here.
However, the resulting code doesn't include property initializers. For example, the following JSON:
{
"Name" : "Blastoise"
}
gets deserialized to this:
public class RootObject
{
public string Name { get; set; }
}
Presumably this is by design, since the values used in the JSON will probably be overridden anyways, so adding initializers might just annoy people who don't want them.
But what if I want that? Short of manually adding every value by hand, is there a way to deserialize JSON to the following?
public class RootObject
{
public string Name { get; set; } = "Blastoise";
}
Obviously in this case a manual edit is easy, but manual editing becomes tedious for larger JSON objects.
is there a way to deserialize JSON to the following?
Using the source code of the converter you mentioned.
A quick change at the line 204
sw.WriteLine(prefix + "public {0} {1} {{ get; set; }} = {2};", field.Type.GetTypeName(), field.MemberName, field.GetExamplesText());
gives me the result similar to what you described
internal class SampleResponse1
{
[JsonProperty("Name")]
public string Name { get; set; } = "Blastoise";
}

How to deserialize an immutable data structure?

How would I deserialize YAML to a immutable data structure?
e.g. I have this YAML:
Value: SomeString
Number: 99
And this data structure:
public class MyData
{
public MyData(string value, int number)
{
Value = value;
Number = number;
}
public string Value { get; }
public int Number { get; }
}
For this I'd to use the constructor. So somehow I'd need to first retrieve a Dictionary<string, object> parsed from the YAML respecting my class (so 99 would be int, not string), then scan my type for an appropriate constructor,
Although the question doesn't mention it, I'm assuming you are using YamlDotNet (or SharpYaml which is a fork of YamlDotNet)
YamlDotNet doesnt support deserializing into classes that do not have a default constructor - but one option to achieve what you want is to deserialize into an intermediate Builder type that is mutable which can produce the final type.
e.g.
public class MyDataBuilder
{
public string Value { get; set; }
public int Number { get; set; }
public MyData Build() => new MyData(Value, Number);
}
And then use something like:
deserializer.Deserialize<MyDataBuilder>(yaml).Build();
You would end up having to create a parallel set of builders for your whole model however, e.g. if MyData had a third parameter of type MyOtherData (I've changed the example to use records instead of classes to make it concise):
public record MyOtherData(string OtherValue);
public record MyData(string Value, int Number, MyOtherData otherData);
In which case we would need another Builder:
public class MyOtherDataBuilder
{
public string OtherValue { get; set; }
}
And MyDataBuilder would look like:
public class MyDataBuilder
{
public string Value { get; set; }
public int Number { get; set; }
public MyOtherDataBuilder MyOtherData { get; set; }
public MyData Build() => new MyData(Value, Number, MyOtherData.Build());
}
It's an old but surprisingly relevant question. Now, with records in C#, immutable collections in .net, lack of ability to deserialize immutable data is a blocker - there is no way we need to change all our data types just to be able to deserialize. One practical workaround that I found - is to convert yaml to json first, then deal with json your preferred way - System.Text.Json, Newtonsoft, etc.
Here is how to do is easiest way:
static string ConvertToJson(string yaml) {
object DeserializeYaml() =>
new DeserializerBuilder()
.Build()
.Deserialize(new StringReader(yaml))
?? throw new InvalidOperationException("Cannot deserialize yaml string:" + Environment.NewLine + yaml);
string SerializeYamlObjectToJson(object yamlObject) =>
new SerializerBuilder()
.JsonCompatible()
.Build()
.Serialize(yamlObject);
return SerializeYamlObjectToJson(DeserializeYaml());
}
The only disadvantage, potentially big, is performance. I feel, however, that it's rarely an important requirement for yaml.
use the FormatterServices.GetUninitializedObject API (this will NOT invoke any constructors at all) and then use reflection to set fields.
Code example:
var instance = FormatterServices.GetUninitializedObject(typeof(MyData));
var flags = BindingFlags.NonPublic | BindingFlags.Instance;
var type = typeof(MyData);
var stringField = type.GetField("_value", flags);
stringField.SetValue(instance, "SomeString");
var numberField = type.GetField("_number", flags);
numberField.SetValue(instance, 99);
MyData data = (MyData)instance;

How to Deserialize XML elements to generic list with unknown element names

I am retrieving and successfully deserializing an xml string from a database table column for the known element names (these do not change) but there are also some nested XML Elements called "Other Attributes" which are not always going to be known. I'm having some trouble deserialising these unknown element names to a generic list so I can display them in html once deserialised.
The XML is as follows:
<Detail>
<DetailAttributes>
<Name>Name_123</Name>
<Type>Type_123</Type>
</DetailAttributes>
<OtherAttributes>
<SummaryKey AttributeName="SummaryKey">SummaryKey_123</SummaryKey>
<Account AttributeName="Account">Account_123</Account>
</OtherAttributes>
</Detail>
I have no problem deserialising the 'Name' and 'Type' elements and I can deserialise the 'SummaryKey' and 'Account' elements but only if I explicitly specify their element names - which is not the desired approach because the 'OtherAttributes' are subject to change.
My classes are as follows:
[XmlRoot("Detail")]
public class objectDetailsList
{
[XmlElement("DetailAttributes"), Type = typeof(DetailAttribute))]
public DetailAttribute[] detailAttributes { get; set; }
[XmlElement("OtherAttributes")]
public List<OtherAttribute> otherAttributes { get; set; }
public objectDetailsList()
{
}
}
[Serializable]
public class Detail Attribute
{
[XmlElement("Type")]
public string Type { get;set; }
[XmlElement("Name")]
public string Name { get;set; }
public DetailAttribute()
{
}
}
[Serializable]
public class OtherAttribute
{
//The following will deserialise ok
//[XmlElement("SummaryKey")]
//public string sumKey { get; set; }
//[XmlElement("Account")]
//public string acc { get; set; }
//What I want to do below is create a list of all 'other attributes' without known names
[XmlArray("OtherAttributes")]
public List<Element> element { get; set; }
}
[XmlRoot("OtherAttributes")]
public class Element
{
[XmlAttribute("AttributeName")]
public string aName { get; set; }
[XmlText]
public string aValue { get; set; }
}
When I try to retrieve the deserialised list of OtherAttribute elements the count is zero so it's not able to access the elements nested within "Other Attributes".
Can anybody help me with this please?
With concrete classes and dynamic data like this, you won't be able to lean on the standard XmlSerializer to serialize / deserialize for you - as it reflects on your classes, and the properties you want to populate simply don't exist. You could provide a class with all possible properties if your set of 'OtherAttributes' is known and finite, and not subject to future change, but that would give you an ugly bloated class (and I think you've already decided this is not the solution).
Practical options therefore:
Do it manually. Use the XmlDocument class, load your data with .Load(), and iterate the nodes using .SelectNodes() using an XPath query (something like "/Detail/OtherAttributes/*"). You will have to write the lot yourself, but this gives you complete control over the serialization / deserialization. You won't have to cover your code in (arguably superfluous!) attributes either.
Use Json.NET (http://james.newtonking.com/json), it allows for far greater control over serialization and deserialization. It's fast, has good docs and is overall pretty nifty really.

Categories