This question already has answers here:
How can I parse a JSON string that would cause illegal C# identifiers?
(3 answers)
Closed 3 years ago.
I am working on code that processes responses from OpenLibrary. This is a rest service that returns books based on a passed in ISBN. An example URL is this:
https://openlibrary.org/api/books?bibkeys=ISBN:9780596005405&jscmd=data&format=json
{
ISBN:9780596005405: {
publishers: [
{
name: "O'Reilly"
}
],
pagination: "xxxii, 854 p. :",
identifiers: {
lccn: [
"2006281089"
],
openlibrary: [
"OL17924716M"
],
isbn_10: [
"0596005407"
],
goodreads: [
"58129"
],
librarything: [
"187028"
],
},
.... other properties omitted for brevity
}
I have these objects:
public class OLResult
{
Publishers Publishers { get; set; }
// other properties
}
public class Publishers
{
// properties go here
}
I created C# objects that are identical to what OpenLibrary returns. However, if you look at the response, you'll notice it's a JSON with a root element with a weird key: ISBN: followed by the passed in ISBN number. Newtonsoft.Json does not know how to map this dynamic key to my OLResult object.
I have created a simple converter to manually convert this JSON to the correct object:
public class OpenLibraryResultConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
OLResult result = new OLResult();
// Perform magic to copy JSON results to result object.
return result;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
I have also added an attribute [JsonConverter(typeof(OpenLibraryResultConverter))] to my OLResult class. When I call JsonConvert.DeserializeObject<OLResult>(jsonStringResult);, and use the debugger to check the ReadJson method, I can not seem to find the JSON data of the jsonStringResult parameter I passed in to the converter. Most properties of the parameters passed into the ReadJson method appear null or empty.
My question: how do I successfully read out the JSON string in my new JsonConverter?
In case you don't need that dynamic property number: ISBN:XXXXXX you can go with something simple like using partial JSON serialization that you can check out here
It's not clear from the post., but the first part is actually a label, so the overall structure is like this:
{
"ISBN:9780596005405": {...}
}
Note that the label contains a colon, which makes it more difficult to translate directly into a C# object.
But either way, you'll need to either define an object class for that and parse it, or as #Ernestas suggests, skip it.
Related
This question already has answers here:
Best way to upgrade JSON field to a class structure
(1 answer)
Json.NET custom serialization with JsonConverter - how to get the "default" behavior
(1 answer)
Closed 1 year ago.
I'm trying to make a "reference" system, where JSON files can depend and reference other files. I know JSON.net has a built-in system like this but it doesn't fit my use case.
I implemented it like this:
public class ReferenceJsonConverter : JsonConverter<NamedType>
{
private readonly ContentLoader _contentLoader;
public ReferenceJsonConverter(ContentLoader contentLoader)
{
_contentLoader = contentLoader;
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, NamedType value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override NamedType ReadJson(JsonReader reader, Type objectType, NamedType existingValue, bool hasExistingValue,
JsonSerializer serializer)
{
switch (reader.TokenType)
{
case JsonToken.String:
{
string value = reader.Value!.ToString().ToLower();
return _contentLoader.Load<NamedType>(value);
}
default:
{
NamedType namedType = (NamedType?) serializer.Deserialize(reader, objectType);
return namedType;
}
}
}
}
So pretty simple, if the token is a string it gets it from memory instead of deserializing it. The problem is, since the token type is the same as the parent type, this will loop infinitely because it will keep trying to use ReferenceJsonConverter to deserialize non-strings.
I've tried the following:
Re-implement CanConvert to try and "skip" StartObject tokens (janky, didn't work)
Have a different JsonConverter do the StartObject, then deserialize using ReferenceJsonConverter on string tokens (loops infinitely because I can't "skip" the converter)
Re-implement the JObject convert method (wayy too much work, will break on updates, and it's all internal anyways)
For clarity, it would look like this:
{ <--- Serialized NamedType, should use default deserializer
"Name": "Test", <-- Field of NamedType, should use default deserializer
"ReferenceOther": "ReferencedType" <--- Referenced NamedType, should use ReferenceJsonConverter
}
This question already has answers here:
How to handle both a single item and an array for the same property using JSON.net
(9 answers)
Closed 2 years ago.
So my API service provider has inconsistent JSON response body elements. Specifically, a property that can contain one or many records isn't always passed back as an array. If there are multiple records it is. But if there is only one record then the record is passed back as a singleton.
What I am looking to do in my C# app is to programmatically append the [ ] around the singleton record, so that it's correctly defined as an array with a single element. More of a regex experiment, since I am handling the JSON response body as a string. I know it's perhaps less efficient, but the JSON string is what a third-party "black box" is looking for.
Example of the singleton JSON response body.
{
"Transfer":{
"transferID":"3581",
"sent":"true",
"received":"true"
}
}
And now an example of the array JSON body.
{
"Transfer":[
{
"transferID":"3581",
"sent":"true",
"received":"true"
},
{
"transferID":"3582",
"sent":"true",
"received":"true"
}
]
}
Just looking for the quickest and cleanest way to add the [ and the ] around the singleton Transfer record. Would like a reusable method, since the API service provider passes back other record types this way as well.
Apparently the most intuitive way to handle the inconsistency is described here --> How to handle both a single item and an array for the same property using JSON.net.
Specifically, here is the C# class that facilities what's required.
class SingleOrArrayConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(List<T>));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
return token.ToObject<List<T>>();
}
return new List<T> { token.ToObject<T>() };
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
This question already has answers here:
How to make JSON.Net serializer to call ToString() when serializing a particular type?
(4 answers)
Closed 3 years ago.
Suppose I have some class that defines ToString():
class Person {
public override string ToString() { /* ... */ }
}
And suppose an instance is contained in some model:
public Person Person { get; }
Then it is serialized like this:
"person": {
"value": "Foo Bar"
}
But what I expected was this:
"person": "Foo Bar"
Can I do this somehow, or must I use a custom converter?
UPDATE
No this is not a dupe of that linked question. That shows how to do two-way conversion to/from a type. I want to do one-way conversion, given my type already has a ToString method - i.e. serialization only, not deserialization.
The question is not how to write a type converter - it is whether this one-way serialization is possible without a type converter.
I recently ran into this on a project, and the only thing my team came to, was having to write a type converter.
I want a one-way conversion which relies on my type's ToString() overload. This is for data serialized on the server and used in a REST response.
This is what I did:
public class StringConverter : JsonConverter {
public override bool CanConvert(Type objectType) {
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
throw new NotSupportedException("This one-way converter cannot be used for deserialization.");
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
writer.WriteValue(value.ToString());
}
}
Used like this:
[JsonConverter(typeof(StringConverter))]
public Person Person { get; } // assumes Person has overloaded ToString()
And the serialized result is this:
"person": "Foo Bar"
(instead of "person": { "value": "Foo Bar" }.)
Maybe a better name is ToStringSerializer or OneWayStringConverter.
Clarification (to anyone in the same situation):
Note that my task is to serialize an existing legacy object. As such, I would prefer to tune the serializer rather than interfere with the data structure.
I believe in most cases it's better to remove the duplicates directly from the data, as indicated by #danny-chen's answer.
As part of my object that I want to serialize with JSON.Net, there is a string[] files property which contains duplicates:
some/path/to/f1.jpg
some/path/to/f1.jpg
some/path/to/f2.jpg
some/path/to/f3.jpg
some/path/to/f2.jpg
And let's suppose these are not necessarily in order (f2, f3, f2).
Is it possible to serialize the array and ignore the duplicates ? Expected result:
{
"files": [
"some/path/to/f1.jpg",
"some/path/to/f2.jpg",
"some/path/to/f3.jpg"
]
}
I have tried the PreserveReferencesHandling setting, but it didn't work as each file in the array is a different object, with a possibly repeated value.
It's not part of the serialization, it's part of the data processing. I suggest you remove the duplicates before serialization.
string[] files = GetFiles();
data.Files = files.Distinct().ToArray();
//serialize data
//instead of data.Files = files; and do tricky things in serialization
The simplest solution is to filter the list before serialization as suggested by #Danny Chen. However, if you absolutely have to do it during serialization you can use a custom JsonConverter.
Here is the code you would need:
public class RemoveDuplicatesConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(IEnumerable<T>).IsAssignableFrom(objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteStartArray();
foreach (T item in ((IEnumerable<T>)value).Distinct())
{
serializer.Serialize(writer, item);
}
writer.WriteEndArray();
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use the converter, add a [JsonConverter] attribute to the list or array property in your class for which you'd like to remove duplicates, as shown below. Be sure the generic type of the converter matches the type of your list.
class Foo
{
[JsonProperty("files")]
[JsonConverter(typeof(RemoveDuplicatesConverter<string>))]
public string[] Files { get; set; }
}
Then serialize as normal. The list in the JSON will have the duplicates removed, but the original list in your object will be unaffected.
string json = JsonConvert.SerializeObject(your_object, Formatting.Indented);
Fiddle: https://dotnetfiddle.net/vs2oWQ
I have the following JSON:
[
{
"name": "codeURL",
"value": "abcd"
},
{
"name": "authURL",
"value": "fghi"
}
]
I created the following objects:
public class ConfigUrlModel {
[JsonProperty("name")]
public abstract string name { get; set; }
[JsonProperty("value")]
public abstract string value { get; set; }
}
public class ConfigUrlsModel {
[JsonProperty]
public List<ConfigUrlModel> ConfigUrls { get; set; }
}
I am deserializing with the following line:
resultObject = Newtonsoft.Json.JsonConvert.DeserializeObject<ConfigUrlsModel>(resultString);
ConfigUrlsModel result = resultObject as ConfigUrlsModel;
I am getting the following error:
Exception JsonSerializationException with no inner exception: Cannot deserialize JSON array into type 'Microsoft.Xbox.Samples.Video.Services.Jtv.Models.ConfigUrl.ConfigUrlsModel'.
Exception JsonSerializationException with no inner exception: Cannot deserialize JSON array into type 'Microsoft.Xbox.Samples.Video.Services.Jtv.Models.ConfigUrl.ConfigUrlsModel'.
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.EnsureArrayContract(Type objectType, JsonContract contract)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList(JsonReader reader, Type objectType, JsonContract contr at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.EnsureArrayContract(Type objectType, JsonContract contract)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList(JsonReader reader, Type objectType, JsonContract contrNavigationService:OnNavigateToMessage PageSourceUri=/Microsoft.Xbox.Sample.UI;component/ErrorPrompt/ErrorPromptView.xaml
What am I doing wrong? How do I fix this?
The root JSON container is an array, not an object, so deserialize it thusly:
var configUrls = Newtonsoft.Json.JsonConvert.DeserializeObject<List<ConfigUrlModel>>(resultString);
var result = new ConfigUrlsModel { ConfigUrls = configUrls }; // If you still need the root object.
A JSON array is an ordered list of values [value1, value2, ..., value], which is what is shown in your question. Json.NET will convert .NET arrays and collections to JSON arrays, so you need to deserialize to a collection type.
The JSON you are sending is an array, but you are attempting to deserialize it to an object. Ether change your JSON so it matches the object defintion at the top level, and has a matching attribute, like this:
{
"ConfigUrls":[
{
"name":"codeURL",
"value":"abcd"
},
{
"name":"authURL",
"value":"fghi"
}
]
}
Or change your deserialize call to:
var urls = DeserializeObject<List<ConfigUrlModel>>(json);
This will return a List<ConfigUrlModel> which you can either use directly, or wrap in a ConfigUrlModels instance if you need to.
Additionally it is possible to deserialize this JSON directly to the desired class, by creating a custom newtonsoft JsonConverter sublcass. But that's going to make the code a little less clear, so avoid it if possible.