How to serialize and deserialize an object as a string? - c#

I would like to serialize (and deserialize) an C# object to a json string.
Normally when objects are serialized, objects are enclosed in a pair of {} in the generated json.
But here I am interested in object to be serialized to only a string.
I am interested in doing this to encapsulate logic about how ItemNumbers should be formatted.
But I am not interested expose the fact that I am using a class for the ItemNumber instead of an ordinary string.
Here is an example of what I am looking for.
The class ItemNumber is contained in the class Item.
public class ItemNumber
{
private string _value;
public ItemNumber(string num)
{
_value = num;
}
}
public class Item
{
public ItemNumber ItemNumber { get; set; }
}
public void Main()
{
var itemNumber = new ItemNumber("ABC-1234");
var item = new Item
{
ItemNumber = itemNumber,
};
var json = System.Text.Json.JsonSerializer.Serialize(item);
}
I would like for an Item to be serialized to json looking like this:
{
"itemNumber": "ABC-1234" // <- Notice, not an object. Just a string
}
I understand that I probably have to implement custom serializer, but the guide have found seems to assume that a C# object should always be serialized to a json object.
How do implement the serialization logic I am looking for?

The answer for how to do this is explained in the System.Text.Json documentation. Implement a custom converter and use that during serialization.
Note: it is not possible to extract the value from ItemNumber in your example, I implemented the ToString method.
public class ItemNumber
{
private string _value;
public ItemNumber(string num)
{
_value = num;
}
public override string ToString()
{
return _value;
}
}
public class Item
{
// rename output property
[JsonPropertyName("itemNumber")]
// use custom converter
[JsonConverter(typeof(ItemNumberJsonConverter))]
public ItemNumber ItemNumber { get; set; }
}
public class ItemNumberJsonConverter : JsonConverter<ItemNumber>
{
public override ItemNumber Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options) =>
new ItemNumber(reader.GetString()!);
public override void Write(
Utf8JsonWriter writer,
ItemNumber val,
JsonSerializerOptions options) => writer.WriteStringValue(val.ToString());
}
internal class Program
{
static void Main(string[] args)
{
var itemNumber = new ItemNumber("ABC-1234");
var item = new Item
{
ItemNumber = itemNumber,
};
var serializeOptions = new JsonSerializerOptions
{
WriteIndented = true,
};
var json = System.Text.Json.JsonSerializer.Serialize(item, serializeOptions);
Console.WriteLine(json);
}
}
console output
{
"itemNumber": "ABC-1234"
}

you will not need any custom serialiazers, if you add some attributes to your classes
public class Item
{
[System.Text.Json.Serialization.JsonIgnore]
public ItemNumber ItemNumber { get; set; }
[System.Text.Json.Serialization.JsonPropertyName("itemNumber")]
public string _itemNumberValue
{
get { return ItemNumber==null? null: ItemNumber.ToString(); }
set {ItemNumber = new ItemNumber(value);}
}
public Item(string itemNumber)
{
ItemNumber = new ItemNumber(itemNumber);
}
public Item() { }
}

I dont know why you wont just do this???
Add ? if you want to allow the values of the object to be nullable.
public class ItemNumber
{
public string? Number { get; set; }
public ItemNumber(string itemNumber)
{
this.Number = itemNumber;
}
}
public class Item
{
//Can be any data type / object
public int? ItemNumber { get; set; }
public List<int>? ItemNumbers { get; set; }
public ItemNumber? Number { get; set; }
}
and then...
//Where ever you're populating the object
var itemNumbers = new Item
{
ItemNumber = 1,
ItemNumbers = new List<int> { 1, 2, 3, 4 },
Number = new ItemNumber("ABC-1234")
};
//Write this output to a new file or and existing one to update it.
var output = JsonConvert.SerializeObject(itemNumbers);
/* Output
* {
* "ItemNumber": 1,
* "ItemNumbers": [1,2,3,4],
* "Number": {"Number":"ABC-1234"}
* }
*/
Edit: Would highly recommend using Newtonsoft.

Related

System.Text.Json Deserialization using constructor

I am using System.Text.Json for deserialization.
I want to use the constructor in SerialNo(string serialNo) to build my object.
public class SerialNo
{
[JsonConstructor]
public SerialNo(string serialNo)
{
if (serialNo == null) throw new ArgumentNullException(nameof(serialNo));
if (string.IsNullOrWhiteSpace(serialNo)) throw new Exception("My exception text");
Value = serialNo.Trim('0');
}
public string Value { get; set; }
}
public class Item
{
public SerialNo SerialNo { get; set; }
public string AffiliationOrgCode { get; set; }
}
public class Root
{
public List<Item> Item { get; set; }
}
public class DeserializationTestsWithSystemTextJson
{
private const string JsonString = #"
{
""item"": [
{
""serialNo"": ""000000000002200878"",
""affiliationOrgCode"": ""OrgCode1""
},
{
""serialNo"": ""000000000002201675"",
""affiliationOrgCode"": ""OrgCode1""
}
]
}
";
[Fact]
public void Simple_Deserialization_With_SystemTextJson()
{
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web);
var deserializedClass = JsonSerializer.Deserialize<Root>(JsonString, options);
Assert.NotNull(deserializedClass);
Assert.Equal("2201675", deserializedClass.Item[1].SerialNo.Value);
}
}
Fails with:
The JSON value could not be converted to JsonNetDeserialization.Test.WithSystemText.SerialNo. Path: $.item[0].serialNo | LineNumber: 3 | BytePositionInLine: 44.
Any ideas?
Since you are converting a string to a SerialNo, you need a custom converter. For example:
public class SerialNoConverter : JsonConverter<SerialNo>
{
public override SerialNo? Read(ref Utf8JsonReader reader, Type typeToConvert,
JsonSerializerOptions options)
{
var serialNo = reader.GetString();
return new SerialNo(serialNo);
}
public override void Write(Utf8JsonWriter writer, SerialNo value,
JsonSerializerOptions options)
{
// Left for OP if required
throw new NotImplementedException();
}
}
And to use it you can either add an attribute to your class:
public class Item
{
[JsonConverter(typeof(SerialNoConverter))]
public SerialNo SerialNo { get; set; }
public string AffiliationOrgCode { get; set; }
}
Or add the converter to your serialiser options:
options.Converters.Add(new SerialNoConverter());
Here is a running example.
You are able to use a constructor to deserialize JSON using System.Text.Json, contrary to what the marked answer says. Even Microsoft documents it: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/immutability?pivots=dotnet-7-0
There are a some drawbacks though:
All class/struct fields/properties must match the JSON property name exactly
All json properties must not be nested in a deeper JSON object
To deserialize JSON with a constructor, you just need to use [JsonConstructor]. Here's an example of deserializing this way:
JSON:
{
"Fp": 2.199,
"Pi": 3.14159,
"Str": "Hello World"
}
The deserialized class:
public class Dto
{
public double Fp { get; set; }
public double Pi { get; set; }
public string Str { get; set; }
[JsonConstructor]
public Dto(double fp, double pi, string str)
{
Fp = fp;
Pi = pi;
Str = str;
}
public override string ToString()
{
return $"{nameof(Fp)}: {Fp}, {nameof(Pi)}: {Pi}, {nameof(Str)}: {Str}";
}
}
Now to deserialize, you just include this expression: var dto = JsonSerializer.Deserialize<Dto>(Json); In the event you are trying to deserialize are custom type, you can include the [JsonConverter(...)] attribute over the custom type properties/fields in your deserialized class if the custom types do not already have a JsonConverter attribute for their class. Because the class properties use the exact name as in the Json, all other types are still deserialized properly and the custom type property will use the attributed custom converter. Here is an example of using a custom converter:
public class Dto
{
public double Fp { get; set; }
public double Pi { get; set; }
[JsonConverter(typeof(CustomStringConverter))] // This can be removed if the CustomString class already has a JsonConverter attribute
public CustomString Str { get; set; }
[JsonConstructor]
public Dto(double fp, double pi, CustomString str)
{
this.Fp = fp;
Pi = pi;
Str = str;
}
public override string ToString()
{
return $"{nameof(Fp)}: {Fp}, {nameof(Pi)}: {Pi}, {nameof(Str)}: {Str}";
}
}

Deserialize Json document when everything in source is an array, but target object type is not

I have a JSON object where all properties are arrays regardless of if they really should be or not.
{
"StringValue": ["mystringvalue"],
"StringArrayValue": ["value1","value2"],
"LongValue": [123]
}
Meanwhile, my class knows the types they should actually be
class MyClass {
public string StringValue {get;set;}
public string[] StringArrayValue {get;set;}
public long LongValue {get;set;}
}
When I do JsonSerializer.Deserialize(jsondoc), it fails because it cannot convert the arrays to non-arrays. How can I have the deserializer pick the first value in the returned JSON data if the class type is not an array?
If you don't want to make things too complex, you can make an additional class for the Deserialization
class MyClassOriginalValue {
public string[] StringValue {get;set;}
public string[] StringArrayValue {get;set;}
public long[] LongValue {get;set;}
public MyClass GetMyClass()
{
return new MyClass
{
StringValue = this.StringValue?.FirstOrDefault(),
StringArrayValue = this.StringArrayValue,
LongValue = this.LongValue?.FirstOrDefault() ?? 0
}
}
}
Here my results:
void Main()
{
var jsonOrig = $"{{" +
"\"StringValue\": [\"mystringvalue\"]," +
"\"StringArrayValue\": [\"value1\",\"value2\"]," +
"\"LongValue\": [123]" +
"}";
var originalJsonValue = JsonSerializer.Deserialize<MyClassOriginalValue>(jsonOrig);
var targetValue = originalJsonValue.GetMyClass();
Console.WriteLine(JsonSerializer.Serialize(targetValue));
}
One approach would be to map the array values to a backing store that is an array, but add a getter-setter adapter that allows you to use the singular StringValue and LongValue properties transparently. The "original" singular get-set properties themselves will be ignored by Json per [JsonIgnore] attribute.
class MyClass
{
[JsonPropertyName("StringValue")]
public string[] StringValueBackingStore { get; set; } = new string[1];
[JsonIgnore]
public string StringValue
{
get => StringValueBackingStore.FirstOrDefault();
set
{
if(StringValueBackingStore.Length == 1)
{
StringValueBackingStore[0] = value;
}
else
{
Debug.Assert(false, "Expecting array length of 1");
}
}
}
public string[] StringArrayValue { get; set; }
[JsonPropertyName("LongValue")]
public long[] LongValueBackingStore { get; set; } = new long[1];
[JsonIgnore]
public long LongValue
{
get => LongValueBackingStore.FirstOrDefault();
set
{
if (LongValueBackingStore.Length == 1)
{
LongValueBackingStore[0] = value;
}
else
{
Debug.Assert(false, "Expecting array length of 1");
}
}
}
}
TEST SERDES
static void Main(string[] args)
{
var myClass = JsonSerializer.Deserialize<MyClass>(jsondoc);
Console.WriteLine($"{myClass.StringValue} {myClass.LongValue} {string.Join(",", myClass.StringArrayValue)}");
Console.WriteLine($"\n{JsonSerializer.Serialize(myClass)}");
}
#jimi had the right idea. Here is what I ended up with.
public class GetFirstElementJsonConverter<T> : JsonConverter<T>
{
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.StartArray)
{
reader.Read();
var elements = new List<T>();
while (reader.TokenType != JsonTokenType.EndArray)
{
elements.Add(JsonSerializer.Deserialize<T>(ref reader, options)!);
reader.Read();
}
return elements.FirstOrDefault();
}
return JsonSerializer.Deserialize<T>(ref reader, options);
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
JsonSerializer.Serialize(writer, value, options);
}
}
And then apply it like:
class MyClass {
[JsonConverter(typeof(GetFirstElementJsonConverter<string>))]
public string StringValue {get;set;}
public string[] StringArrayValue {get;set;}
[JsonConverter(typeof(GetFirstElementJsonConverter<long>))]
public long LongValue {get;set;}
}

How to deserialize Arraylist items into concrete type using System.Text.Json?

I have below Test classes
public class Test
{
public int MyProperty { get; set; }
public ArrayList list { get; set; }
}
public class Employee
{
public int MyProperty { get; set; }
public string Name { get; set; }
}
and the below code
static void Main(string[] args)
{
Employee e1 = new Employee();
e1.MyProperty = 1;
e1.Name = "Yash";
Employee e2 = new Employee();
e2.MyProperty = 1;
e2.Name = "Yash";
Test t = new Test();
t.list = new ArrayList();
t.list.Add(e1);
t.list.Add(e2);
string text = JsonSerializer.Serialize(t);
Test t2 = new Test();
t2 = JsonSerializer.Deserialize<Test>(text);
}
When I deserialize with Newtonsoft I see JObjects instead of Employee objects in the array List. I was able to solve this using Newtonsoft.Json by creating ArrayList converter.
However, the same is not possible with System.text.Json. How can I deserialize this ArrayList with System.Text.Json? It's age old code I need to support, and every time I deserialize an ArrayList I know which object type it is expected to contain.
If you know in advance that the ArrayList should contain items of a certain type, you can easily create a JsonConverter<ArrayList> to serialize and deserialize the ArrayList as follows:
public class KnownItemTypeArrayListConverter<TItem> : JsonConverter<ArrayList>
{
public override ArrayList Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
new ArrayList(JsonSerializer.Deserialize<List<TItem>>(ref reader, options));
public override void Write(Utf8JsonWriter writer, ArrayList value, JsonSerializerOptions options) =>
JsonSerializer.Serialize(writer, value.Cast<TItem>(), options);
}
And then you can use it to serialize and deserialize your Test model as follows:
var e1 = new Employee { MyProperty = 1, Name = "Yash", };
var e2 = new Employee { MyProperty = 1, Name = "Yash", };
var t = new Test { list = new ArrayList { e1, e2 }, };
var options = new JsonSerializerOptions
{
Converters = { new KnownItemTypeArrayListConverter<Employee>() },
};
var text = JsonSerializer.Serialize(t, options);
var t2 = JsonSerializer.Deserialize<Test>(text, options);
Demo fiddle #1 here.
Alternatively, you can apply the converter to your model by using JsonConverterAttribute:
public class Test
{
public int MyProperty { get; set; }
[System.Text.Json.Serialization.JsonConverter(typeof(KnownItemTypeArrayListConverter<Employee>))]
public ArrayList list { get; set; }
}
(I am using the fully qualified type name to avoid confusion with Newtonsoft's JsonConverterAttribute.)
Demo fiddle #2 here.
That being said, use of ArrayList is deprecated. From the docs:
We don't recommend that you use the ArrayList class for new development. Instead, we recommend that you use the generic List<T> class.
You might consider upgrading your legacy code to use the following model:
public class Test
{
public int MyProperty { get; set; }
public List<Employee> list { get; set; }
}

How do I re-architect my model types to reduce code replication

I have 3 model types:
public class BankA_Transaction : BanKTransactionMetaData
{
public string GROUPName { get; set; }
public string ACC_ID { get; set; }
public string ACCOUNT_NO { get; set; }
}
public class BankB_Transaction : BanKTransactionMetaData
{
public string Name { get; set; }
public string ACC_ID { get; set; }
public string ACCOUNT_NO { get; set; }
}
public class BankC_Transaction : BanKTransactionMetaData
{
public string FullName { get; set; }
public string ACC_ID { get; set; }
public string ACCOUNT_NO { get; set; }
}
Note: The actual property lists are much longer
All of which inherit some fields needed when saving into the database.
public class BanKTransactionMetaData
{
public String BankName { get; set; }
}
These models get filled with records from a file sent by the bank and then saved to a database.
As part of this save I convert the records to JSON as that is required by the database.
public void SaveBankA(BankA bankA)
{
bankA.BankName = "Bank_A";
string jsonText = JsonConvert.SerializeObject(bankA_Transaction, Formatting.Indented);
Code for saving...
At the moment I have a different methods for SaveBankA, SaveBankA and SaveBankB.
It seems to me that this is code replication and that I should get all the models to inherit better in order to use a base type? instead of each named type.
I've read up on Abstract and Virtual classes as I suspect that's what I need but I can't work out how to plug it together.
I can't just use Object in SaveBankA as I need to add .BankName.
Is there a better architecture to reduce code replication?
Perhaps you need something like this?
In base service class:
protected void SaveBankTransaction(BankTransactionMetaData tran)
{
string jsonText = JsonConvert.SerializeObject(tran, Formatting.Indented);
// additional saving code
}
In child service classes:
public void SaveBankA(BankA bankA)
{
bankA.BankName = "Bank_A";
base.SaveBankTransaction(bankA);
}
Create a couple of interfaces, one for your meta data (IBankData) and one for your bank transaction details (IBankTransaction). The IBankData interface will maintain a reference to the IBankTransaction interface. This should also allow you to add additional banks when needed, e.g. Bank D.
public interface IBankData
{
string BankName { get; }
// ... additional bank meta data properties
// ...
IBankTransaction Transaction { get; set; }
}
public interface IBankTransaction
{
[JsonProperty("ACC_ID")]
string AccountId { get; set; }
[JsonProperty("ACCOUNT_NO")]
string AccountNumber { get; set; }
// ... additional shared bank transaction properties
// ...
}
FYI, I chose to use the JsonProperty attribute to control the name for the JSON key, this allows the class properties to be named according to best practices without affecting the JSON property names.
Next implement the interfaces for each bank you will be working with. In each bank add the additional properties that will only apply to each implementation, i.e. since the GroupName property is only used by BankA, this property will be added to the BankA class and not the interface. The same goes for any other bank specific properties.
Bank A
public class BankA : IBankData
{
public string BankName => "BankA";
public IBankTransaction Transaction { get; set; }
}
public class BankATransaction : IBankTransaction
{
// Bank A specific properties
[JsonProperty("GROUPName")]
public string GroupName { get; set; }
// ... additional Bank A specific properties
// ...
// interface implemented properties
public string AccountId { get; set; }
public string AccountNumber { get; set; }
}
Bank B
public class BankB : IBankData
{
public string BankName => "BankB";
public IBankTransaction Transaction { get; set; }
}
public class BankBTransaction : IBankTransaction
{
// Bank B specific properties
public string Name { get; set; }
// ... additional Bank B specific properties
// ...
// interface implemented properties
public string AccountId { get; set; }
public string AccountNumber { get; set; }
}
Bank C
public class BankC : IBankData
{
public string BankName => "BankC";
public IBankTransaction Transaction { get; set; }
}
public class BankCTransaction : IBankTransaction
{
// Bank B specific properties
public string FullName { get; set; }
// ... additional Bank B specific properties
// ...
// interface implemented properties
public string AccountId { get; set; }
public string AccountNumber { get; set; }
}
JsonConverter
Since the IBankTransaction is a property within the IBankData this will change your JSON structer. You may not want this, to retain your structure, a JsonConverter can be implemented on the IBankData interface. This will remove the Transaction object in the JSON and move the child properties under the JSON root.
public class BankJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken t = JToken.FromObject(value);
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
}
else
{
JObject o = (JObject)t;
JProperty transactionProperty = o.Properties().FirstOrDefault(p => p.Name == "Transaction");
o.Remove("Transaction");
JToken token = transactionProperty;
foreach (JToken ct in token.Children())
{
foreach (var prop in JProperty.FromObject(ct))
{
o.Add(prop);
}
}
serializer.Serialize(writer, o);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
}
public override bool CanRead => false;
public override bool CanConvert(Type objectType)
{
return objectType.GetInterfaces().Contains(typeof(IBankData));
}
}
Usage
For the usage example I've created a few test functions to prep the data and added SaveBank method that you can relocate in your actual code as it would make sense for your solution.
class Program
{
static void Main(string[] args)
{
string bankATransJson = GetBankATestJsonInput();
BankATransaction bankATransaction = JsonConvert.DeserializeObject<BankATransaction>(bankATransJson);
BankA bankA = new BankA();
bankA.Transaction = bankATransaction;
Console.WriteLine(SaveBank(bankA));
// output:
// {
// "BankName": "BankA",
// "GROUPName": "g54321",
// "ACC_ID": "A01",
// "ACCOUNT_NO": "A1111"
// }
string bankBInputJson = GetBankBTestJsonInput();
BankBTransaction bankBTransInput = JsonConvert.DeserializeObject<BankBTransaction>(bankBInputJson);
BankB bankB = new BankB();
bankB.Transaction = bankBTransInput;
Console.WriteLine(SaveBank(bankB));
// output:
// {
// "BankName": "BankB",
// "ACC_ID": "B02",
// "ACCOUNT_NO": "B2222",
// "Name": "Bank_Of_B
// }
string bankCInputJson = GetBankCTestJsonInput();
BankCTransaction bankCTransInput = JsonConvert.DeserializeObject<BankCTransaction>(bankCInputJson);
BankC bankC = new BankC();
bankC.Transaction = bankCTransInput;
Console.WriteLine(SaveBank(bankC));
// output:
// {
// "BankName": "BankC",
// "ACC_ID": "C03",
// "ACCOUNT_NO": "C3333",
// "FullName": "C Bank"
// }
}
public static string SaveBank(IBankData bankData)
{
// when calling the serialize object method, we pass our BankJsonConverter
string jsonText = JsonConvert.SerializeObject(bankData, Formatting.Indented, new BankJsonConverter());
// this example just returns the JSON text
// but you would implement your save logic as needed
return jsonText;
}
private static string GetBankATestJsonInput()
{
var obj = new { ACC_ID = "A01", ACCOUNT_NO = "A1111", GROUPName = "g54321" };
return JsonConvert.SerializeObject(obj);
}
private static string GetBankBTestJsonInput()
{
var obj = new { ACC_ID = "B02", ACCOUNT_NO = "B2222", Name = "Bank_Of_B" };
return JsonConvert.SerializeObject(obj);
}
private static string GetBankCTestJsonInput()
{
var obj = new { ACC_ID = "C03", ACCOUNT_NO = "C3333", FullName = "C Bank" };
return JsonConvert.SerializeObject(obj);
}
}

Inheritance from Jobject Newtonsoft

Inheritance from Jobject(Newtonsoft) the existents properties from class not serialized.
Why were the Id and Name properties not serialized?
public class Test : JObject
{
public int Id { get; set; }
public string Name { get; set; }
}
class Program
{
static void Main(string[] args)
{
var test = new Test();
test["new_pro"] = 123456;
test.Id = 1;
test.Name = "Dog";
var r = Newtonsoft.Json.JsonConvert.SerializeObject(test);
// Result = { "new_pro":123456}
}
}
Any idea?
Whatever is the reason you want to do that - the reason is simple: JObject implements IDictionary and this case is treated in a special way by Json.NET. If your class implements IDictionary - Json.NET will not look at properties of your class but instead will look for keys and values in the dictionary. So to fix your case you can do this:
public class Test : JObject
{
public int Id
{
get { return (int) this["id"]; }
set { this["id"] = value; }
}
public string Name
{
get { return (string) this["name"]; }
set { this["name"] = value; }
}
}
If you just want to have both dynamic and static properties on your object - there is no need to inherit from JObject. Instead, use JsonExtensionData attribute:
public class Test {
public int Id { get; set; }
public string Name { get; set; }
[JsonExtensionData]
public Dictionary<string, JToken> AdditionalProperties { get; set; } = new Dictionary<string, JToken>();
}
var test = new Test();
test.AdditionalProperties["new_pro"] = 123456;
test.Id = 1;
test.Name = "Dog";
var r = Newtonsoft.Json.JsonConvert.SerializeObject(test);

Categories