In order to ask my question, I will be referring to @Brian Rogers 's answer here. In this answer ReadJson is doing something relatively simple. How could I however add a layer in there in order to manipulate the incoming Json string before deserialising it into an object and then returning it?
Here is the type of things I would like to do (modified version of Brian's WrappedObjectConvert class):
class WrappedObjectConverter : JsonConverter
{
private string CustomParsing(string jsonString)
{
string modifiedJsonString;
// Some renaming
modifiedJsonString= Regex.Replace(modifiedJsonString, $@"(?<="")CarName(?="":\s)", "Myname", RegexOptions.IgnoreCase);
modifiedJsonString= Regex.Replace(modifiedJsonString, $@"(?<="")CustName(?="":\s)", "Myname", RegexOptions.IgnoreCase);
modifiedJsonString= Regex.Replace(modifiedJsonString, $@"(?<="")MyName(?="":\s)", "Myname", RegexOptions.IgnoreCase);
modifiedJsonString= Regex.Replace(modifiedJsonString, $@"(?<="")SomeAddr(?="":\s)", "AddressLine1 ", RegexOptions.IgnoreCase);
// Renaming IsPublic true/false to IsPrivate false/true
modifiedJsonString= Regex.Replace(modifiedJsonString, "\"IsPublic\": true,", "\"IsPrivate\": false,", RegexOptions.IgnoreCase);
modifiedJsonString = Regex.Replace(modifiedJsonString, "\"IsPublic\": false,", "\"IsPrivate\": true,", RegexOptions.IgnoreCase);
}
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
string modifiedJsonString = CustomParsing(token.ToString());
return ????; // How to return the object
// I could do something of the sort, but not sure it's got its place here:
// return JsonConvert.DeserializeObject<RootObject>(modifiedJsonString );
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
The client class has also been slightly modified by adding the field "IsPrivate":
public class Client
{
[JsonConverter(typeof(WrappedObjectConverter))]
public List<Product> ProductList { get; set; }
[JsonConverter(typeof(WrappedObjectConverter))]
public string Name { get; set; }
[JsonConverter(typeof(WrappedObjectConverter))]
public bool IsPrivate { get; set; }
[JsonConverter(typeof(WrappedObjectConverter))]
public string AddressLine1 { get; set; }
}
And the demo with a modified Json (some labels have been changed from Brian's example, which need to be parsed and modified):
class Program
{
static void Main(string[] args)
{
string json = @"
{
""Result"": {
""Client"": {
""ProductList"": {
""Product"": [
{
""MyName"": {
""CarName"": ""Car polish"",
""IsPublic"": ""True""
}
}
]
},
""MyName"": {
""CustName"": ""Mr. Clouseau""
},
""AddressLine1"": {
""SomeAddr"": ""Hightstreet 13""
}
}
}
}";
RootObject obj = JsonConvert.DeserializeObject<RootObject>(json);
Client client = obj.Result.Client;
foreach (Product product in client.ProductList)
{
Console.WriteLine(product.Name);
}
Console.WriteLine(client.Name);
Console.WriteLine(client.AddressLine1);
}
}
As you can see, the way the parsing is being done is a bit hacky, so my questions are:
From your question and comments it sounds like you have some complex JSON and your goal is to flatten it down into a simpler class structure. But you have some additional constraints:
You can do all this with a custom JsonConverter
. The key is to load the JSON data into a JObject
inside the converter. From there you can use SelectToken
to specify paths to retrieve specific pieces of data from the JObject
. You can then use these pieces to construct your objects via their non-default constructors. At the same time you can translate any values that require it.
For example, let's say you are starting with the JSON in your question, and the classes you really want to deserialize to look like this:
public class Client
{
public Client(string name, string addressLine1, List<Product> productList)
{
Name = name;
AddressLine1 = addressLine1;
ProductList = productList;
}
public List<Product> ProductList { get; set; }
public string Name { get; set; }
public string AddressLine1 { get; set; }
}
public class Product
{
public Product(string name, bool isPrivate)
{
Name = name;
IsPrivate = isPrivate;
}
public string Name { get; set; }
public bool IsPrivate { get; set; }
}
Here is a custom converter that will handle the deserialization:
class CustomConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Client);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject obj = JObject.Load(reader);
string name = (string)obj.SelectToken("Result.Client.MyName.CustName");
string addressLine1 = (string)obj.SelectToken("Result.Client.AddressLine1.SomeAddr");
List<Product> productList = obj.SelectToken("Result.Client.ProductList.Product")
.Select(jt =>
{
string prodName = (string)jt.SelectToken("MyName.CarName");
bool isPublic = string.Equals((string)jt.SelectToken("MyName.IsPublic"), "True", StringComparison.OrdinalIgnoreCase);
return new Product(prodName, !isPublic);
})
.ToList();
Client client = new Client(name, addressLine1, productList);
return client;
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use the converter, you can either add a [JsonConverter]
attribute to the Client
class like this:
[JsonConverter(typeof(CustomConverter))]
public class Client
{
...
}
Or you can pass the converter as a parameter to JsonConvert.DeserializeObject()
like this:
Client client = JsonConvert.DeserializeObject<Client>(json, new CustomConverter());
Here is a working demo: https://dotnetfiddle.net/EwtQHh