Deserialize a json string when there is no field name.in c# - c#

Normally when I use API's and get a Json string back I simply make a class to suit the string and populate that class using newton JsonConvert.DeserializeObject.
However I now have a Json string which 1 of the fields does not have a name.
{
"attacks": {
"114862720": {
"code": "115dc2b990153c41c33d519b26cc302a",
"timestamp_started": 1596782220,
"timestamp_ended": 1596782226,
"attacker_id": 580816,
"attacker_name": "chedders",
"attacker_faction": 32585,
"attacker_factionname": "Heart of a Pirate",
"defender_id": 65306,
"defender_name": "-Clansdancer",
"defender_faction": 0,
"defender_factionname": null,
"result": "Attacked",
"stealthed": 0,
"respect_gain": 4.14,
"chain": 3,
"modifiers": {
"fairFight": 3,
"war": 1,
"retaliation": 1,
"groupAttack": 1,
"overseas": 1,
"chainBonus": 1
}
},
"114862829": {
"code": "8bf08c8ceb9b72f05f40235310cd822e",
"timestamp_started": 1596782339,
"timestamp_ended": 1596782344,
"attacker_id": 580816,
"attacker_name": "chedders",
"attacker_faction": 32585,
"attacker_factionname": "Heart of a Pirate",
"defender_id": 964979,
"defender_name": "brko21",
"defender_faction": 0,
"defender_factionname": null,
"result": "Attacked",
"stealthed": 0,
"respect_gain": 4.11,
"chain": 4,
"modifiers": {
"fairFight": 3,
"war": 1,
"retaliation": 1,
"groupAttack": 1,
"overseas": 1,
"chainBonus": 1
}
}
}
}
After attacks is an ID which is unique to each entry.so building a class for this as I normally would just wont work as the ID is unknown.
Any pointers on how to deserialise this string would be most welcome.

You can use the [JsonExtensionData] property.
Here's the official example:
public class DirectoryAccount
{
// normal deserialization
public string DisplayName { get; set; }
// these properties are set in OnDeserialized
public string UserName { get; set; }
public string Domain { get; set; }
[JsonExtensionData]
private IDictionary<string, JToken> _additionalData;
[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
// SAMAccountName is not deserialized to any property
// and so it is added to the extension data dictionary
string samAccountName = (string)_additionalData["SAMAccountName"];
Domain = samAccountName.Split('\\')[0];
UserName = samAccountName.Split('\\')[1];
}
public DirectoryAccount()
{
_additionalData = new Dictionary<string, JToken>();
}
}
So in your OnDeserializing method, you can get the values from the dictionary and add them to a proper field/cast them to a list of objects, etc.

This would deserialise to a Dictionary<string, Attack>. Where Attack is a type that you define with all of the properties within each object, in the JSON document.
This example assumes you're using NewtonSofts JSON library:
var attacks = JsonConvert.DeserializeObject<Dictionary<string, Attack>>(jsonString);
public class Attack {
[JsonProperty("code")]
public string Code { get; set; }
[JsonProperty("timestamp_started")]
public long TimestampStarted { get; set; }
[JsonProperty("timestamp_ended")]
public long TimestampEnded { get; set; }
[JsonProperty("attacker_id")]
public int AttackerId { get; set; }
[JsonProperty("attacker_name")]
public string AttackerName { get; set; }
[JsonProperty("attacker_faction")]
public int AttackerFaction { get; set; }
[JsonProperty("attacker_factionname")]
public string AttackerFactionName { get; set; }
[JsonProperty("defender_id")]
public int DefenderId { get; set; }
[JsonProperty("defender_name")]
public string DefenderName { get; set; }
[JsonProperty("defender_faction")]
public int DefenderFaction { get; set; }
[JsonProperty("defender_factionname")]
public string DefenderFactionName { get; set; }
[JsonProperty("result")]
public string Result { get; set; }
[JsonProperty("stealthed")]
public int Stealthed { get; set; }
[JsonProperty("respect_gain")]
public decimal RespectGain { get; set; }
[JsonProperty("chain")]
public int Chain { get; set; }
[JsonProperty("modifiers")]
public Dictionary<string, int> Modifiers { get; set; }
}
This results in a collection of identifiers against a strongly typed field set.
var allAttacksByFaction = attacks.Where(x => x.Value.AttackerFaction == 1234);
var singleAttack = attacks.Single(x => x.Key == "<value of attack identifier>");

Try this json converter.
class AttacksConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Attacks[]);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject attacks = serializer.Deserialize<JObject>(reader);
Dictionary<string, AttackDetails> result = new Dictionary<string, AttackDetails>();
foreach (JProperty property in attacks.Properties())
{
string attackKey = property.Name;
Attacks attackValue = property.Value.ToObject<Attacks>();
result.Add(attackKey, new AttackDetails()
{
Code = attackValue.Code
});
}
return result;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
The full code can be found here: https://github.com/tmutton/StackOverflowQuestion63544325

Related

Deserialize complex json into an object

I have a strange json, which:
I can not change it, it is from a third party
It can have up to 75 properties
A simple example of the json:
{
"bookingcode":["ABC","DEF", "GHJ", "TEST"],
"referencenumber":[123, 456]
"bookingdate":["22-07-2022T14:00:30", "23-11-2022T17:00:25"]
}
I am only interested in the last value of the array and want to deserialize into a class called Booking, so in this case bookingcode="TEST" and bookingdate="23-11-2022T17:00:25".
A solution would be define a model:
public class Booking
{
public string BookingCode { get; set; }
public int ReferenceNumber { get; set; }
public DateTime BookingDate { get; set;}
}
public class BookingInput
{
public List<string> BookingCode { get; set;}
public List<int> ReferenceNumber { get; set; }
public List<DateTime> BookingDate { get; set; }
}
var bookinginput = JsonSerializer.Deserialize<BookingInput>(jsonString);
var booking = new Booking
{
BookingCode = bookinginput.BookingCode.Last(),
ReferenceNumber = bookinginput.ReferenceNumber.Last(),
...
}
I think it is very tedious to write out all 75 properties in code like this. Is there a better solution in Json.net or System.Text.Json for this?
Using NewtonSoft.Json, you can create your own JsonConverter. Below I have created a LastArrayItemJsonConverter, which takes the last item from an array of a certain type and returns that.
public class LastArrayItemJsonConverter<TItem> : JsonConverter<TItem>
{
private string _formatString;
public LastArrayItemJsonConverter()
{ }
public LastArrayItemJsonConverter(string formatString)
{
_formatString = formatString;
}
public override TItem ReadJson(JsonReader reader, Type objectType, TItem existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (typeof(TItem) == typeof(DateTime) || typeof(TItem) == typeof(DateTime?))
reader.DateFormatString = _formatString;
TItem result = default;
if (reader.TokenType != JsonToken.StartArray)
return default;
while (reader.Read() && reader.TokenType != JsonToken.EndArray)
result = (TItem)Convert.ChangeType(reader.Value, typeof(TItem));
return result;
}
public override void WriteJson(JsonWriter writer, TItem value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
By decorating your model, you can specify that the serializer should use the converter to convert the properties:
public class Booking
{
[JsonConverter(typeof(LastArrayItemJsonConverter<string>))]
public string BookingCode { get; set; }
[JsonConverter(typeof(LastArrayItemJsonConverter<int>))]
public int ReferenceNumber { get; set; }
[JsonConverter(typeof(LastArrayItemJsonConverter<DateTime>), "dd-MM-yyyy\\THH:mm:ss")]
public DateTime BookingDate { get; set; }
}
Now, the model's properties will be populated with the last values from the arrays. Deserialize the json using:
var booking = JsonConvert.DeserializeObject<Booking>(json)
You can define a single Booking class which includes
the BookingInput's properties
and the last item retrieval logics as well
public class Booking
{
[JsonIgnore]
public string BookingCode => BookingCodes.Last();
[JsonIgnore]
public int ReferenceNumber => ReferenceNumbers.Last();
[JsonIgnore]
public DateTime BookingDate => BookingDates.Last();
[JsonProperty("bookingcode")]
public List<string> BookingCodes { get; set; } = new List<string>();
[JsonProperty("referencenumber")]
public List<int> ReferenceNumbers { get; set; } = new List<int>();
[JsonProperty("bookingdate")]
public List<DateTime> BookingDates { get; set; } = new List<DateTime>();
}
if you want to use your custom c# class
using Newtonsoft.Json;
var jsonParsed = JObject.Parse(json);
foreach (var arr in jsonParsed.Properties())
jsonParsed[arr.Name] = arr.Value.LastOrDefault();
Booking booking = jsonParsed.ToObject<Booking>();
but since you can have up to 75 properties, maybe it would be better to use a dictionary instead of a custom class
Dictionary<string, object> bookingDict = new Dictionary<string, object>();
foreach (var arr in jsonParsed.Properties())
bookingDict.Add(arr.Name, arr.Value.LastOrDefault());
result
{
"bookingcode": "TEST",
"referencenumber": 456,
"bookingdate": "23-11-2022T17:00:25"
}
It's better if you provide your request code too. did you add ReadAsStringAsync(); after the request?
public class Booking
{
[JsonProperty("bookingcode")]
public List<string> BookingCodes { get; set; } = new List<string>();
[JsonProperty("referencenumber")]
public List<int> ReferenceNumbers { get; set; } = new List<int>();
[JsonProperty("bookingdate")]
public List<DateTime> BookingDates { get; set; } = new List<DateTime>();
}
And some web request sample
using (var client = new HttpClient())
{
var response = await client.
GetAsync(apiEndpoint);
var responseText = await response.Content.ReadAsStringAsync();
var compareResult = JsonSerializer.Deserialize<Booking>(responseText)??new Booking();
return compareResult;
}

How Can I deserialize a json into a structure?

I have a class mapping like this:
public class Settings
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("content")]
public ContentStructure Content { get; set; }
}
public struct ContentStructure
{
public Content ContentClass;
public string ContentString;
public static implicit operator ContentStructure(Content content) => new ContentStructure { ContentClass = content };
public static implicit operator ContentStructure(string #string) => new ContentStructure { ContentString = #string };
}
public class Content
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("duration")]
public long Duration { get; set; }
}
When I attempt to deserialize the following JSON string:
{
"id": "any_id",
"type": "any_type",
"content": {
"id": "any_id",
"duration": 1000
}
}
I always get the deserialized settings object with the property settings.Content.ContentClass null, but whenever my JSON string has the property "content" as string (instead of an object) the structure field ContentString comes correctly. What I am doing wrong? How can I can convert the JSON string above correctly?
Yet another solution could be to make use of the JsonSchema
First let's redefine your data model:
public abstract class Settings
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
}
public class SettingsV1 : Settings
{
[JsonProperty("content")]
public string Content { get; set; }
}
public class SettingsV2 : Settings
{
[JsonProperty("content")]
public Content Content { get; set; }
}
public class Content
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("duration")]
public long Duration { get; set; }
}
Instead of having a middle man (ContentStructure) rather than you can have two separate Settings versions
The common fields are defined inside the abstract base class
Now, you can use these two versioned classes to define json schemas:
private static JSchema schemaV1;
private static JSchema schemaV2;
//...
var generator = new JSchemaGenerator();
schemaV1 = generator.Generate(typeof(SettingsV1));
schemaV2 = generator.Generate(typeof(SettingsV2));
Finally all you need to do is to do a preliminary check before calling the DeserializeObject with the proper type:
Settings settings = null;
var semiParsed = JObject.Parse(json);
if (semiParsed.IsValid(schemaV1))
{
settings = JsonConvert.DeserializeObject<SettingsV1>(json);
}
else if (semiParsed.IsValid(schemaV2))
{
settings = JsonConvert.DeserializeObject<SettingsV2>(json);
}
else
{
throw new NotSupportedException("The provided json format is not supported");
}
Use a Custom JsonConverter. Modify it based on your need.
[JsonConverter(typeof(ContentStructureConverter))]
public struct ContentStructure
{
public Content ContentClass;
public string ContentString;
public static implicit operator ContentStructure(Content content) => new ContentStructure { ContentClass = content };
public static implicit operator ContentStructure(string #string) => new ContentStructure { ContentString = #string };
}
public class ContentStructureConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ContentStructure);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
ContentStructure contentStruct;
if (reader.ValueType == typeof(string))
contentStruct = reader.Value as string;
else
contentStruct = serializer.Deserialize<Content>(reader);
return contentStruct;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
ContentStructure? contentStruct = value as ContentStructure?;
if (contentStruct.HasValue && contentStruct.Value.ContentClass != null)
serializer.Serialize(writer, contentStruct.Value.ContentClass);
else
serializer.Serialize(writer, contentStruct.Value.ContentString);
}
}
The input json is wrong for your format.
If you use this json instead,
{
"id": "any_id",
"type": "any_type",
"content": {
"ContentClass" : {
"id": "any_id",
"duration": 1000
}
}
}
This way, it will work.
settings.Content.ContentClass is three layer but your is json two layer (settings.Content). So after "content", it is looking for id and duration which ContentStructure does not have these two fields.
My reasoning is when it encounters a value type (like {"field" : "value}), it will look for value type, like string, int or double. When it encounters a json type (like {"field" : {another json here} }), it will look for class or struct.

Deserialize property to different class based on other property c#

I know there are many similar questions on SO, however all the ones I've found require a shared base class in order to work.
With a stream of JSON data like this:
[
{
"webhookType": "order",
"data": {
"id": "eeiefj393",
"orderProperty": "Value"
}
},
{
"webhookType": "customer",
"data": {
"id": 29238,
"customerProperty": "Value"
}
}
]
I wish to deserialize this into two containers, List<Customer> and List<Order>. Where the two classes are as follows:
class Order
{
public string Id { get; set; }
public string OrderProperty { get; set; }
[...]
}
class Customer
{
public long Id { get; set; }
public string CustomerProperty { get; set; }
[...]
}
There may be shared property names however there are no shared properties + type between these two classes and so the solutions involing a sub class aren't working for me.
You need to create a JsonConverter.
DataConverter
public class WebHookConverter : 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)
{
if (reader.TokenType == JsonToken.StartObject)
{
JObject item = JObject.Load(reader);
if (item["webhookType"].Value<string>() == "order")
{
var webhook = new WebHook
{
Type = item["webhookType"].Value<string>(),
Data = item["data"].ToObject<Order>()
};
return webhook;
}
else if (item["webhookType"].Value<string>() == "customer")
{
var webhook = new WebHook
{
Type = item["webhookType"].Value<string>(),
Data = item["data"].ToObject<Customer>()
};
return webhook;
}
}
return null;
}
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}
}
Objects
[JsonConverter(typeof(WebHookConverter))]
public class WebHook
{
[JsonProperty("webhookType")]
public string Type { get; set; }
public object Data { get; set; }
}
public class Order
{
public string Id { get; set; }
[JsonProperty("orderProperty")]
public string Property { get; set; }
}
public class Customer
{
public long Id { get; set; }
[JsonProperty("customerProperty")]
public string Property { get; set; }
}
Serialization
var json = File.ReadAllText("json1.json");
var obj = JsonConvert.DeserializeObject<List<WebHook>>(json);
var orderList = obj.Where(o => o.Type == "order").Select(o => o.Data).ToList();
var customerList = obj.Where(o => o.Type == "customer").Select(o => o.Data).ToList();
Output:

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);
}
}

Modify the behaviour of JSON.NET to serialize a collection of objects to an array of IDs

I want to modify JSON.NET so that when I am serializing a Model from my API it sends only an array of IDs for a composite Collection object.
For example:
class Employee
{
public ICollection<Address> Addresses { get; set; }
}
class Address
{
public int id;
public string location;
public string postcode;
}
Then when I send that back through WebApi
Request.Createresponse(HttpStatusCode.OK, new Employee());
Instead of this:
{
"Addresses" :
[
{"id" : 1, "location" : "XX", "postcode" : "XX" },
{"id" : 2, "location" : "XX", "postcode" : "XX" }
]
}
It just sends as this:
{
"Addresss" : [1,2]
}
I want this to be happening application-wide and I don't want to modify at the specific place.
How can I achieve this using the JSON.NET serializer?
You can get the result you want using a custom JsonConverter such as this:
class IdOnlyListConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (typeof(IEnumerable).IsAssignableFrom(objectType) &&
objectType != typeof(string));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JArray array = new JArray();
foreach (object item in (IEnumerable)value)
{
PropertyInfo idProp = item.GetType().GetProperty("id");
if (idProp != null && idProp.CanRead)
{
array.Add(JToken.FromObject(idProp.GetValue(item, null)));
}
}
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();
}
}
In your model, wherever you have a collection of something where you only want the IDs, decorate the collection property with a [JsonConverter] attribute specifying the custom converter. For example:
class Employee
{
public string name { get; set; }
[JsonConverter(typeof(IdOnlyListConverter))]
public ICollection<Address> Addresses { get; set; }
}
class Address
{
public int id { get; set; }
public string location { get; set; }
public string postcode { get; set; }
}
When the collection gets serialized, the converter will be used, and only the ID values will be written out. Demo:
class Program
{
static void Main(string[] args)
{
Employee emp = new Employee
{
name = "Joe",
Addresses = new List<Address>
{
new Address { id = 1, location = "foo", postcode = "bar" },
new Address { id = 2, location = "baz", postcode = "quux" }
}
};
string json = JsonConvert.SerializeObject(emp);
Console.WriteLine(json);
}
}
Output:
{"name":"Joe","Addresses":[1,2]}
Please have a look at Json.Net's documentation.
public class Address
{
[JsonProperty("Id")]
public int I'd { get; set; }
[JsonIgnore]
public string Location { get; set; }
[JsonIgnore]
public string PostalCode { get; set; }
}
The only drawback to this is that you'll never be able to serialize the Location and PostalCode properties to JSON should you ever want to.
I believe there is a way to specify the serialization that should be used when serializing to JSON with Json.Net. Again, have a look at their documentation.

Categories