How to convert a json within a json into object? - c#

So I got this guy
{
"lng_x": "106.883368",
"style": "{\"name\":\"TACTICAL\"}"
}
Which I wanted to make into this guy
public class Root
{
public string lng_x { get; set; }
public Style style { get; set; }
}
public class Style
{
public string name { get; set; }
}
But I got this instead
public class Root
{
public string lng_x { get; set; }
public string style { get; set; }
}
I know that the Json is supposed to be like this
{
"lng_x": "106.883368",
"style":
{
"name": "TACTICAL"
}
}
But I can't because it is already like that when I got it.
Is there any way so that I don't have to deserialize it again?

If you can't fix the JSON document you can create a custom JSON type converter and apply it to the style property. And whoever created that document needs to fix their bug.
If you use System.Text.Json, a possible converter could be :
public class StyleStringJsonConverter : JsonConverter<Style>
{
public override Style Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options) =>
JsonSerializer.Deserialize<Style>(reader.GetString()!);
public override void Write(
Utf8JsonWriter writer,
Style style,
JsonSerializerOptions options) =>
writer.WriteStringValue(JsonSerializer.Serialize(style));
}
This can be applied through an attribute :
public class Root
{
public string lng_x { get; set; }
[JsonConverter(typeof(StyleStringJsonConverter))]
public Style style { get; set; }
}
JSON.NET also has custom converters:
public class StyleStringConverter : JsonConverter<Style>
{
public override void WriteJson(JsonWriter writer, Style value, JsonSerializer serializer)
{
writer.WriteValue(JsonConvert.SerializeObject(value));
}
public override Style ReadJson(JsonReader reader, Type objectType, Style existingValue, bool hasExistingValue, JsonSerializer serializer)
{
string s = (string)reader.Value;
return JsonConvert.DeserializeObject<Style>(s);
}
}
This can be applied using an attribute too:
public class Root
{
public string lng_x { get; set; }
[JsonConverter(typeof(StyleStringJsonConverter))]
public Style style { get; set; }
}

As stated before, the original creators should fix your the JSON file.
Meanwhile, you are stuck with it.
To make sure you don't dirty your own code
use Regex to make it a real JSON
Deserialize as normal
Advantage: When the JSON is changed to a correct form you have to remove the CleanJSON Method and your code will stay working.
The Code
using System.Text.Json;
using System.Text.RegularExpressions;
function YourFunction(){
//-- Get your JSON in string format (from API, file,...)
string _jsonString = ... ;
CleanJSON(_jsonString);
YourModels.Root _correctStructure = (JsonSerializer.Deserialize<YourModels.Root>(_JSsonString);
}
function CleanJSON(string jsonString){
//__ Probably you can group the regex
string _regexPatternRoot = #"(\\"")(.*?)(\\)";
string _regexReplacementRoot = #"""$2";
string _regexPatternStyle = "\"({)\"(.*?)\"}\"";
string _regexReplacementStyle = #"$1""$2""}";
_JSsonString = Regex.Replace(_JSsonString, _regexPatternRoot, _regexReplacementRoot);
_JSsonString = Regex.Replace(_JSsonString, _regexPatternStyle, _regexReplacementStyle);
}

you don't need any custom converter. The problem is that your origional style property is a json string and you have to deserialize it. In this case I usually use a JsonConstructor
public class Root
{
public string lng_x { get; set; }
public Style style { get; set; }
[JsonConstructor]
public Root (string style)
{
this.style=JsonConvert.DeserializeObject<Style>(style);
}
public Root (){}
}
test
Root data = JsonConvert.DeserializeObject<Root>(json);
{
"lng_x": "106.883368",
"style": {
"name": "TACTICAL"
}
}

Related

System.Text.Json - Deserialize nested object as string

I'm trying to use the System.Text.Json.JsonSerializer to deserialize the model partially, so one of the properties is read as string that contains the original JSON.
public class SomeModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Info { get; set; }
}
The example code
var json = #"{
""Id"": 1,
""Name"": ""Some Name"",
""Info"": {
""Additional"": ""Fields"",
""Are"": ""Inside""
}
}";
var model = JsonSerializer.Deserialize<SomeModel>(json);
should produce the model, which Info property contains the Info object from the original JSON as string:
{
"Additional": "Fields",
"Are": "Inside"
}
It doesn't work out of the box and throws an exception:
System.Text.Json.JsonException: ---> System.InvalidOperationException:
Cannot get the value of a token type 'StartObject' as a string.
What have I tried so far:
public class InfoToStringConverter : JsonConverter<string>
{
public override string Read(
ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
{
return reader.GetString();
}
public override void Write(
Utf8JsonWriter writer, string value, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
}
and apply it in the model as
[JsonConverter(typeof(InfoToStringConverter))]
public string Info { get; set; }
and add in the options to JsonSerializer
var options = new JsonSerializerOptions();
options.Converters.Add(new InfoToStringConverter());
var model = JsonSerializer.Deserialize<SomeModel>(json, options);
Still, it throws the same exception:
System.Text.Json.JsonException: ---> System.InvalidOperationException:
Cannot get the value of a token type 'StartObject' as a string.
What is the right recipe to cook what I need? It worked in a similar way using Newtonsoft.Json.
Update
For me it is important to keep the nested JSON object as original as possible. So, I'd avoid options like to deserialize as Dictionary and serialize back, because I'm afraid to introduce undesirable changes.
Found a right way how to correctly read the nested JSON object inside the JsonConverter. The complete solution is the following:
public class SomeModel
{
public int Id { get; set; }
public string Name { get; set; }
[JsonConverter(typeof(InfoToStringConverter))]
public string Info { get; set; }
}
public class InfoToStringConverter : JsonConverter<string>
{
public override string Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
using (var jsonDoc = JsonDocument.ParseValue(ref reader))
{
return jsonDoc.RootElement.GetRawText();
}
}
public override void Write(
Utf8JsonWriter writer, string value, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
}
In the code itself there is no need even to create options:
var json = #"{
""Id"": 1,
""Name"": ""Some Name"",
""Info"": {
""Additional"": ""Fields"",
""Are"": ""Inside""
}
}";
var model = JsonSerializer.Deserialize<SomeModel>(json);
The raw JSON text in the Info property contains even extra spaces introduced in the example for nice readability.
And there is no mixing of model representation and its serialization as remarked #PavelAnikhouski in his answer.
You can use a JsonExtensionData attribute for that and declare a Dictionary<string, JsonElement> or Dictionary<string, object> property in your model to store this information
public class SomeModel
{
public int Id { get; set; }
public string Name { get; set; }
[JsonExtensionData]
public Dictionary<string, JsonElement> ExtensionData { get; set; }
[JsonIgnore]
public string Data
{
get
{
return ExtensionData?["Info"].GetRawText();
}
}
}
Then you can add an additional property to get a string from this dictionary by Info key. In code above the Data property will contain the expected string
{
"Additional": "Fields",
"Are": "Inside"
}
For some reasons adding the property with the same name Info doesn't work, even with JsonIgnore. Have a look at Handle overflow JSON for details.
You can also declare the Info property as JsonElement type and get raw text from it
public class SomeModel
{
public int Id { get; set; }
public string Name { get; set; }
public JsonElement Info { get; set; }
}
var model = JsonSerializer.Deserialize<SomeModel>(json);
var rawString = model.Info.GetRawText();
But it will cause a mixing of model representation and its serialization.
Another option is to parse the data using JsonDocument, enumerate properties and parse them one by one, like that
var document = JsonDocument.Parse(json);
foreach (var token in document.RootElement.EnumerateObject())
{
if (token.Value.ValueKind == JsonValueKind.Number)
{
if(token.Value.TryGetInt32(out int number))
{
}
}
if (token.Value.ValueKind == JsonValueKind.String)
{
var stringValue = token.Value.GetString();
}
if (token.Value.ValueKind == JsonValueKind.Object)
{
var rawContent = token.Value.GetRawText();
}
}
a quick addendum to the accepted answer:
If you need to write raw JSON values as well, here is an implementation of the Write method for the converter:
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
{
using (JsonDocument document = JsonDocument.Parse(value))
{
document.RootElement.WriteTo(writer);
}
}
As outlined in the dotnet runtime repo on github, this seems to be the "proper" way to workaround the fact that they decided not to implement a WriteRawValue method.

Trouble deserializing JSON (got value array when single value expected)

I am reading data from a graph database and getting the response as a dynamic object. I go through the results and try to deserialize them as so:
var e = results.GetEnumerator();
while (e.MoveNext())
{
var serialized = JsonConvert.SerializeObject(e.Current);
// {"FlagCalculateclientside":[false],"Description":["Some detailed info"], "Name": ["MyDetailedEntity"]}
var val = JsonConvert.DeserializeObject<MyObject>(serialized);
}
public class MyObject
{
public bool FlagCalculateclientside { get; set; }
public string Description { get; set; }
public string Name { get; set; }
}
But I get the following error:
Newtonsoft.Json.JsonReaderException: Unexpected character encountered while parsing value: [. Path 'FlagCalculateclientside', line 1, position 28.
at Newtonsoft.Json.JsonTextReader.ReadAsBoolean()
at Newtonsoft.Json.JsonReader.ReadForType(JsonContract contract, Boolean hasConverter)
...
I guess this is because the values are in arrays, but only a single value per key was expected.
Any idea how to fix this?
Your model doesn't match your JSON, all of the properties are arrays, in other words they are surround with [...]. To fix, change the model to this:
public class MyObject
{
public List<bool> FlagCalculateclientside { get; set; }
public List<string> Description { get; set; }
public List<string> Name { get; set; }
}
An alternative would be to use a custom converter, for example:
public class ArrayConverter<T> : JsonConverter<T>
{
public override T ReadJson(JsonReader reader, Type objectType, T existingValue, bool hasExistingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
//This isn't the best code but shows you what you need to do.
return token.ToObject<List<T>>().First();
}
public override void WriteJson(JsonWriter writer, T value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
And change your model to this:
public class MyObject
{
[JsonConverter(typeof(ArrayConverter<bool>))]
public bool FlagCalculateclientside { get; set; }
[JsonConverter(typeof(ArrayConverter<string>))]
public string Description { get; set; }
[JsonConverter(typeof(ArrayConverter<string>))]
public string Name { get; set; }
}

C# How to deserialize the Json to a generic entity use base-class?

I need to convert JSON object to entity,
I have many entities and I don't want to write my code some times, so I built an entity-base class and I want to do deserialize to entity (without know which derived-entity call to the base-entity).
Can I do it?
This is my base class (It is abstract class)
public abstract class AbstractEntity
{
EntityState EntityState { get; set; }
DateTime CreatedDate { get; set; }
DateTime? ModifiedDate { get; set; }
string CreatedBy { get; set; }
string ModifiedBy { get; set; }
public EntityState getEntityState()
{
return EntityState;
}
public void SetEntityState(EntityState entityState)
{
EntityState = entityState;
}
}
The first ent:
public class TABLE1: AbstractEntity
{
public TABLE1();
public string ID{ get; set; }
public string ADDRESS{ get; set; }
public virtual ICollection<TABLE2> TABLE2 { get; set; }
}
The second ent:
public class TABLE2: AbstractEntity
{
public TABLE2();
public string ID{ get; set; }
public string ADDRESS{ get; set; }
public virtual TABLE1 TABLE1{ get; set; }
}
The duplicate linked is probably a better general-purpose solution:
public class AbstractEntityConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(AbstractEntity);
}
public override object ReadJson(JsonReader reader,
Type objectType, object existingValue, JsonSerializer serializer)
{
JObject item = JObject.Load(reader);
var type = item["TYPE"].Value<string>();
switch (type)
{
case "TABLE1":
return item.ToObject<TABLE1>();
case "TABLE2":
return item.ToObject<TABLE2>();
default:
return null;
}
}
public override void WriteJson(JsonWriter writer,
object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Usage:
string json = "{\"TYPE\":\"TABLE1\",\"CreatedDate\":\"2018-06-10T08:00:00.000Z\",\"CreatedBy\":\"John\",\"ID\":\"1\",\"ADDRESS\":\"1 Road Street\",\"TABLE2\":[{\"CreatedDate\":\"2018-06-10T08:00:00.000Z\",\"CreatedBy\":\"John\",\"ID\":\"2\",\"ADDRESS\":\"2 Road Street\"}]}";
var settings = new JsonSerializerSettings()
{
Converters = new List<JsonConverter>()
{
new AbstractEntityConverter()
}
};
var obj = JsonConvert.DeserializeObject<AbstractEntity>(json, settings);
Of course, you can define your JsonSettings at a central location so that you don't have to keep writing the declaration every time you use it.
P.S. I've opted to assume a "type" property since you didn't show your JSON, or your method of determining which class to deserialize to. Replace with your own logic as needed.
Try it online
You can use JObject from Newtonsoft.Json library. This is the site https://www.newtonsoft.com/json to refer on how to use it. And, a lot of code samples as well can be found here https://www.newtonsoft.com/json/help/html/Samples.htm.
For example:
using Newtonsoft.Json.Serialization;
public void Foo(string jsonData){
var objData = (JObject)JsonConvert.DeserializeObject(jsonData); // Deserialize json data
dynamic jObject = new JObject();
jObject.ID = objData.Value<string>("ID");
jObject.Address = objData.Value<string>("Address");
jObject.TABLE2 = objData.Value<JArray>("TABLE2");
}
In the above code sample, jObject which has a dynamic type that can be converted to which type you want.
Hope this helps.

How should I parse JSON which has its keys and values' accents escaped without affecting the escapings in the field values?

Querying the Stack Overflow websockets with 155-questions-active I get the following (malformatted) JSON:
{
"action":"155-questions-active",
"data":
"{
\"siteBaseHostAddress\":\"stackoverflow.com\",
\"id\":23747905,
\"titleEncodedFancy\":\"Load sqlite extension in Django\",
\"bodySummary\":\"I have built a sqlite <snip>\",
\"tags\":[\"django\",\"sqlite\",\"pysqlite\"],
\"lastActivityDate\":1400544795,
\"url\":\"http://stackoverflow.com/questions/23747905/<snip>\",
\"ownerUrl\":\"http://stackoverflow.com/users/1311165/pro-chats\",
\"ownerDisplayName\":\"Pro Chats\",
\"apiSiteParameter\":\"stackoverflow\"
}"
}
After applying some fixes
private string MakeJsonCapable(string input)
{
input = input.Trim();
input = input.Replace("data\":\"", "data\":");
input = input.Remove(input.LastIndexOf("\""), 1);
input = input.Replace("\\", string.Empty);
return input;
}
I get to this result:
{
"action": "155-questions-active",
"data": {
"siteBaseHostAddress": "stackoverflow.com",
"id": 23747905,
"titleEncodedFancy": "Load sqlite extension in Django",
"bodySummary": "I have built a sqlite <snip>",
"tags": [
"django",
"sqlite",
"pysqlite"
],
"lastActivityDate": 1400544795,
"url": "http:\/\/stackoverflow.com\/questions\/23747905\/<snip>",
"ownerUrl": "http:\/\/stackoverflow.com\/users\/1311165\/pro-chats",
"ownerDisplayName": "Pro Chats",
"apiSiteParameter": "stackoverflow"
}
}
Which is now acceptable JSON (I'm using some online JSON format tool to verify this) that gets parsed perfectly by JSON.NET.
The problem occurs when a value (so far I've only seen it in bodySummary but I suspect titleEncodedFancy is also likely to have this) contains a ". The literal value that is being passed before making it Json-able is \\\"Compliant Solution\\\": 3 backslashes and an accent.
Note that this is the literal value and does not include any backslashes from the debugger: this is taken directly from the textview; the watch variable shows 7 backslashes.
Obviously this is a problem because now my bodySummary contains an unescaped " which will corrupt the deserializing. For this reason I can't create a custom JsonConverter to escape them myself either since it won't get the right values in the first place.
How can I remove the unwanted backslashes that appear in front of the accents that signify the start and end of a field's name and its value?
Alternatively: maybe I am parsing the data field incorrectly in the first place. If so: what is the correct way?
What you have here is data that has been serialized to a string, placed inside another object and then serialized a second time. To get everything back out correctly, you can reverse the process. Define two classes, one for the outer serialization and one for the inner:
class Outer
{
public string Action { get; set; }
public string Data { get; set; }
}
class Inner
{
public string SiteBaseHostAddress { get; set; }
public int Id { get; set; }
public string TitleEncodedFancy { get; set; }
public string BodySummary { get; set; }
public string[] Tags { get; set; }
public int LastActivityDate { get; set; }
public string Url { get; set; }
public string OwnerUrl { get; set; }
public string OwnerDisplayName { get; set; }
public string ApiSiteParameter { get; set; }
}
Then deserialize like this:
Outer outer = JsonConvert.DeserializeObject<Outer>(json);
Inner inner = JsonConvert.DeserializeObject<Inner>(outer.Data);
When you do this, do NOT apply the "fixes" to the input string. Let the JSON parser do its job.
EDIT
If you want to keep the parent-child relationship, you'll need a custom JsonConverter to handle the deserialization of the child object. To do this, you first need to change the definition of the outer class to this:
class Outer
{
public string Action { get; set; }
[JsonConverter(typeof(InnerConverter))]
public Inner Data { get; set; }
}
Create the InnerConverter class like this:
class InnerConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Inner));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
return JsonConvert.DeserializeObject<Inner>(token.ToString());
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
And finally, you can deserialize like this:
Outer outer = JsonConvert.DeserializeObject<Outer>(json);
Following Brian Rogers' suggestion I created a simple converter which handles it all for me:
Response
public sealed class Response
{
[JsonProperty("action")]
public string Action { get; internal set; }
[JsonProperty("data")]
[JsonConverter(typeof (DataConverter))]
public Data Data { get; internal set; }
}
Data
public sealed class Data
{
[JsonProperty("siteBaseHostAddress")]
public string SiteBaseHostAddress { get; internal set; }
[JsonProperty("id")]
public string Id { get; internal set; }
[JsonProperty("titleEncodedFancy")]
public string TitleEncodedFancy { get; internal set; }
[JsonProperty("bodySummary")]
public string BodySummary { get; internal set; }
[JsonProperty("tags")]
public IEnumerable<string> Tags { get; internal set; }
[JsonProperty("lastActivityDate")]
[JsonConverter(typeof (EpochTimeConverter))]
public DateTime LastActivityDate { get; internal set; }
[JsonProperty("url")]
[JsonConverter(typeof (UriConverter))]
public Uri QuestionUrl { get; internal set; }
[JsonProperty("ownerUrl")]
[JsonConverter(typeof (UriConverter))]
public Uri OwnerUrl { get; internal set; }
[JsonProperty("ownerDisplayName")]
public string OwnerDisplayName { get; internal set; }
[JsonProperty("apiSiteParameter")]
public string ApiSiteParameter { get; internal set; }
}
DataConverter
internal sealed class DataConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof (string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
var value = reader.Value as string;
return JsonConvert.DeserializeObject<Data>(value);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Now I can deserialize it entirely with
var responseObject = JsonConvert.DeserializeObject<Response>(result);

Return unstructured BsonDocument as class-property in ApiController

Similar to this question, I have a class with several different property types, including BsonDocument.
public class Report
{
[BsonId, JsonIgnore]
public ObjectId _id { get; set; }
public string name { get; set; }
[JsonIgnore]
public BsonDocument layout { get; set; }
[BsonIgnore, JsonProperty(PropertyName = "layout")]
public string layout2Json
{
get { return layout.ToJson(); }
}
}
The reason for having BsonDocument in there, is that the layout-property is unstructured and I cannot have any strongly typed sub-classes. Now when the ApiController returns this class, I get something like this:
{
name: "...",
layout: "{type: "...", sth: "..."}"
}
But what I need is the layout-property as an object, not a string.
Is there a way in JSON.NET to plug in a json-string - which is already valid json - as an object and not a string?
The following works, but seems quite wasteful:
[BsonIgnore, JsonProperty(PropertyName = "layout")]
public JObject layout2Json
{
get { return JObject.Parse(layout.ToJson()); }
}
I had a similar issue. I solved it by implementing a custom JsonConverter that will do nothing but to write the raw values (which is already Json) to the Json writer:
public class CustomConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteRaw((string)value);
}
}
Then you use that custom converter to decorate the property that returns the string representation of your BsonDocument object:
public class Report
{
[BsonId, JsonIgnore]
public ObjectId _id { get; set; }
public string name { get; set; }
[JsonIgnore]
public BsonDocument layout { get; set; }
[BsonIgnore, JsonProperty(PropertyName = "layout")]
[JsonConverter(typeof(CustomConverter))]
public string layout2Json
{
get { return layout.ToJson(); }
}
}
That way, you get rid of the double quote issue, and the unstructured object is returned as a valid Json object, not as a string. Hope this help.

Categories

Resources