Unity JSON with mixed media binary objects like audioStream buffers - json

Which JSON library would you use to parse JSON that contains beyond the usual text data, such as audioStream and binary objects buffers?

You may want to provide more information specific to your case.
The solution depends on whether you are:
Going to serialize that data yourself and then deserialize them.
You could use scriptable objects to store audio, sprite, prefab and in general visual-centric data. Then create an editor extension for that particular scriptable object type to expose and edit the JSON data and store them in a .json file in project's assets.
You already have everything serialized in JSON and just need a way to deserialize them.
In this case, you should probably create the Data class to hold those data, with the serializable types in mind. Then create those data and try to import the stream byte array to either an audio file in the file system or an audio clip in memory.
Here is an example that caches the file in a directory:
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using Newtonsoft.Json; // JSON .NET For Unity
using Newtonsoft.Json.Serialization;
[Serializable]
public class MovieModel
{
[Serializable]
public class SlotsData
{
public string Celebrity { get; set; }
public string Genre { get; set; }
}
[Serializable]
public class AudioStreamData
{
public string Type { get; set; }
public byte[] Data { get; set; }
}
public string UserId { get; set; }
public string ContentType { get; set; }
public string IntentName { get; set; }
public SlotsData Slots { get; set; }
public string Message { get; set; }
public string DialogState { get; set; }
public AudioStreamData AudioStream { get; set; }
public override string ToString()
{
return string.Format("[{0}, {1}, {2}, {3}]", UserId, ContentType, IntentName, Message);
}
}
public class MovieWithAudioClip
{
public MovieModel Model { get; set; }
public string CachedFileName { get; set; }
public AudioClip Clip { get; set; }
}
public class AudioClipJSONImporter : MonoBehaviour
{
private static readonly JsonSerializerSettings SERIALIZATION_SETTINGS = new JsonSerializerSettings()
{
// Read the docs to configure the settings based on your data classes and the JSON file itself.
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
private static readonly Dictionary<string, AudioType> WHITELISTED_CONTENT_TYPE_TO_UNITY_AUDIO_TYPE = new Dictionary<string, AudioType>()
{
// Append all the supported content types here with their corresponding type, so that Unity can read them.
{ "audio/mpeg", AudioType.MPEG}
};
private static readonly Dictionary<string, string> CONTENT_TYPE_TO_FILE_EXTENSION = new Dictionary<string, string>()
{
{ "audio/mpeg", ".mp3"}
};
[Header("Drag and drop a JSON movie entry here")]
[SerializeField]
private TextAsset m_MovieEntryJson;
[SerializeField]
private string m_ClipCacheDirectory = "Clips";
[Header("Drag and drop an Audio source here, to preview the current movie entry")]
[SerializeField] AudioSource m_AudioPreviewer;
// Click on the top right of the script when in edit mode to call this procedure.
[ContextMenu("Import JSON entry")]
private void ImportJsonEntry()
{
if (m_MovieEntryJson == null || string.IsNullOrEmpty(m_MovieEntryJson.text))
{
Debug.LogError("Drag and drop a JSON movie entry in the inspector.");
return;
}
MovieModel movieModel = JsonConvert.DeserializeObject<MovieModel>(m_MovieEntryJson.text, SERIALIZATION_SETTINGS);
Debug.LogFormat("Movie entry {0} imported.", movieModel);
Debug.Assert(movieModel != null, "Failed to load movie entry.");
Debug.AssertFormat(movieModel.AudioStream != null, "Failed to load audio stream for movie entry {0}", movieModel);
Debug.AssertFormat(movieModel.AudioStream.Data != null, "Failed to load audio stream byte array for movie entry {0}", movieModel);
if (movieModel == null || movieModel.AudioStream == null || movieModel.AudioStream.Data == null)
{
return;
}
string clipCacheDirName = Application.isPlaying ? Application.persistentDataPath : Application.streamingAssetsPath;
if (!string.IsNullOrEmpty(m_ClipCacheDirectory))
{
clipCacheDirName = Path.Combine(clipCacheDirName, m_ClipCacheDirectory);
}
AudioType supportedAudioType;
string fileExtension = null;
if (!WHITELISTED_CONTENT_TYPE_TO_UNITY_AUDIO_TYPE.TryGetValue(movieModel.ContentType, out supportedAudioType))
{
Debug.LogErrorFormat(
"Failed to load movie {0} with mime type: {1} as it is not in the mime type to extension whitelist.",
movieModel, movieModel.ContentType
);
return;
}
CONTENT_TYPE_TO_FILE_EXTENSION.TryGetValue(movieModel.ContentType, out fileExtension);
StartCoroutine(
GenerateAudioMovie(clipCacheDirName, fileExtension, supportedAudioType, movieModel, (MovieWithAudioClip movie) =>
{
if (m_AudioPreviewer != null)
{
m_AudioPreviewer.clip = movie.Clip;
m_AudioPreviewer.Play();
}
})
);
}
private IEnumerator GenerateAudioMovie(
string rootDirName,
string fileExtension,
AudioType audioType,
MovieModel movieModel,
Action<MovieWithAudioClip> onDone,
Action<string> onError = null
)
{
// Remove this is you can be sure the directory exists.
Directory.CreateDirectory(rootDirName);
// If you can create a non random ID based on the JSON data, that is better.
//
// Mainly, because you can check the file system in case it has already been downloaded and load the clip directly.
// Although, that makes sense only if you have a 'light' route to receive the movie data without the audio stream (which is now cached).
string cachedFileId = Guid.NewGuid().ToString();
string cachedFileName = Path.Combine(rootDirName, cachedFileId + fileExtension);
MovieWithAudioClip audioMovie = new MovieWithAudioClip()
{
Model = movieModel,
CachedFileName = cachedFileName
};
// Source: https://answers.unity.com/questions/686240/audioclip-oggmp3-loaded-from-byte-array.html
//
File.WriteAllBytes(cachedFileName, movieModel.AudioStream.Data);
Debug.LogFormat("Movie audio file exported at: {0}", cachedFileName);
WWW loader = new WWW(string.Format("file://{0}", cachedFileName));
yield return loader;
if (!System.String.IsNullOrEmpty(loader.error))
{
Debug.LogErrorFormat("Failed to load movie {0} at file {1} with error {2}.", movieModel, cachedFileName, loader.error);
if (onError != null)
onError(loader.error);
}
else
{
audioMovie.Clip = loader.GetAudioClip(false, false, audioType);
Debug.AssertFormat(audioMovie.Clip != null, "Failed to generate audio clip for movie entry {0}", movieModel);
if (audioMovie.Clip != null)
{
if (onDone != null)
onDone(audioMovie);
}
else
{
if (onError != null)
onError(loader.error);
}
}
}
}
The code above does not do any processing, it just tries to retrieve an audio clip out of the stream in the JSON and it fails for mp3. However, an audio file will be created in your file system and you can play it and make sure that the JSON parser works.
From there, you need to process the data based on what types you are going to support. Here is a relevant post. Check out the three steps of Kurt-Dekker's answer.
Now, it's up to you to handle the different audio types you want to support, platform compatibility .etc.
Good luck!

I know very little about JSON, and my searches have yielded nothing. Could you tell me a little more on what sort of data you are dealing with?
Try parsing the parts of the file you can with JSON and then for the parts you can't, try building your own parser.
Best of luck.

Related

How to change cas resulting JSON objects back to PascalCase?

I am writing a WebAPICore to return the JSON objects from the database. For unknown reason, the properties are returned as camelCase by default.
I have checked the SQL Script and it does return the correct case for the DataFields. But when I consume the service, the properties of the objects are changed to camelCase automatically.
For example, OfferingID is returned as offeringID
The existing Return JSON object
{
"offeringID": 120842,
"courseCode": "FLTE2A1F/1",
"courseName": "FLT - E2 Certificate in Skills for Working Life (Animals) (QCF)"
}
The format which I want to return
{
"OfferingID": 120842,
"CourseCode": "FLTE2A1F/1",
"CourseName": "FLT - E2 Certificate in Skills for Working Life (Animals) (QCF)"
}
The Model - Offering:
public class Offering
{
[Key]
public int OfferingID { get; set; }
public string CourseCode { get; set; }
public string CourseName { get; set; }
}
My WebAPI Controller Get Method
[HttpGet("{id}")]
public async Task<IActionResult> GetOfferingDetail(int id)
{
var obj = await _context.Offerings.FromSql("dbo.GetOfferingDetail #p0", id).SingleOrDefaultAsync();
if (obj == null)
return NotFound("ID not found");
return new ObjectResult(obj);
}
Configure Services Method in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DbContexts.OakCommonsDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("MyConnection")));
services.AddCors(options => options.AddPolicy("AllowAll", p => p.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()));
var mvccore = services.AddMvc();
mvccore.AddJsonOptions(o => o.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore);
}
Could you please advise me how I could return JSON Objects in the Exact Case as I defined in the Model?
Here is the working code. By default, WebAPI Core is going to use CamelCasePropertyNamesContractResolver(). You need to change it to DefaultContractResolver to render as you defined in the Model.
And DefaultContractResolver is under Newtonsoft.Json.Serialization namespace.
services.AddMvc()
.AddJsonOptions(o => o.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)
.AddJsonOptions(o => o.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver());

Ignore parsing errors during JSON.NET data parsing

I have an object with predefined data structure:
public class A
{
public string Id {get;set;}
public bool? Enabled {get;set;}
public int? Age {get;set;}
}
and JSON is supposed to be
{ "Id": "123", "Enabled": true, "Age": 23 }
I want to handle JSON error in positive way, and whenever server returns unexpected values for defined data-types I want it to be ignore and default value is set (null).
Right now when JSON is partially invalid I'm getting JSON reader exception:
{ "Id": "123", "Enabled": "NotABoolValue", "Age": 23 }
And I don't get any object at all.
What I want is to get an object:
new A() { Id = "123", Enabled = null, Age = 23 }
and parsing warning if possible.
Is it possible to accomplish with JSON.NET?
To be able to handle deserialization errors, use the following code:
var a = JsonConvert.DeserializeObject<A>("-- JSON STRING --", new JsonSerializerSettings
{
Error = HandleDeserializationError
});
where HandleDeserializationError is the following method:
public void HandleDeserializationError(object sender, ErrorEventArgs errorArgs)
{
var currentError = errorArgs.ErrorContext.Error.Message;
errorArgs.ErrorContext.Handled = true;
}
The HandleDeserializationError will be called as many times as there are errors in the json string. The properties that are causing the error will not be initialized.
Same thing as Ilija's solution, but a oneliner for the lazy/on a rush (credit goes to him)
var settings = new JsonSerializerSettings { Error = (se, ev) => { ev.ErrorContext.Handled = true; } };
JsonConvert.DeserializeObject<YourType>(yourJsonStringVariable, settings);
Props to Jam for making it even shorter =)
There is another way. for example, if you are using a nuget package which uses newton json and does deseralization and seralization for you. You may have this problem if the package is not handling errors. then you cant use the solution above. you need to handle in object level. here becomes OnErrorAttribute useful. So below code will catch any error for any property, you can even modify within the OnError function and assign default values
public class PersonError
{
private List<string> _roles;
public string Name { get; set; }
public int Age { get; set; }
public List<string> Roles
{
get
{
if (_roles == null)
{
throw new Exception("Roles not loaded!");
}
return _roles;
}
set { _roles = value; }
}
public string Title { get; set; }
[OnError]
internal void OnError(StreamingContext context, ErrorContext errorContext)
{
errorContext.Handled = true;
}
}
see https://www.newtonsoft.com/json/help/html/SerializationErrorHandling.htm

How can I do JSON serializer ignore navigation properties?

I am exactly in the same case that this question:
How do I make JSON.NET ignore object relationships?
I see the proposed solution and I know I must use a Contract Revolver, and I also see the code of the Contract Resolver, but I do not know how to use it.
Should I use it in the WebApiConfig.vb?
Should I modify my Entity Model anyway?
It is a useful question👍 and I hope this help:
A)
If you have created your models manually (without Entity Framework), mark the relation properties as virtual first.
If your models were created by EF, It has already done it for you and each Relation Property is marked as virtual, as seen below:
Sample class:
public class PC
{
public int FileFolderId {get;set;}
public virtual ICollection<string> Libs { get; set; }
public virtual ICollection<string> Books { get; set; }
public virtual ICollection<string> Files { get; set; }
}
B)
Those relation properties can now be ignored by the JSON serializer by using the following ContractResolver for JSON.NET:
CustomResolver:
class CustomResolver : DefaultContractResolver
{
private readonly List<string> _namesOfVirtualPropsToKeep=new List<string>(new String[]{});
public CustomResolver(){}
public CustomResolver(IEnumerable<string> namesOfVirtualPropsToKeep)
{
this._namesOfVirtualPropsToKeep = namesOfVirtualPropsToKeep.Select(x=>x.ToLower()).ToList();
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty prop = base.CreateProperty(member, memberSerialization);
var propInfo = member as PropertyInfo;
if (propInfo != null)
{
if (propInfo.GetMethod.IsVirtual && !propInfo.GetMethod.IsFinal
&& !_namesOfVirtualPropsToKeep.Contains(propInfo.Name.ToLower()))
{
prop.ShouldSerialize = obj => false;
}
}
return prop;
}
}
C)
Finally, to serialize your model easily use the above ContractResolver. Set it up like this:
// -------------------------------------------------------------------
// Serializer settings
JsonSerializerSettings settings = new JsonSerializerSettings
{
// ContractResolver = new CustomResolver();
// OR:
ContractResolver = new CustomResolver(new []
{
nameof(PC.Libs), // keep Libs property among virtual properties
nameof(PC.Files) // keep Files property among virtual properties
}),
PreserveReferencesHandling = PreserveReferencesHandling.None,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
Formatting = Formatting.Indented
};
// -------------------------------------------------------------------
// Do the serialization and output to the console
var json = JsonConvert.SerializeObject(new PC(), settings);
Console.WriteLine(json);
// -------------------------------------------------------------------
// We can see that "Books" filed is ignored in the output:
// {
// "FileFolderId": 0,
// "Libs": null,
// "Files": null
// }
Now, all the navigation (relation) properties [virtual properties] will be ignored automatically except you keep some of them by determine them in your code.😎
Live DEMO
Thanks from #BrianRogers for his answer here.
If you are using Newtonsoft.Json
Mark field with
Newtonsoft.Json.JsonIgnore
Instead of
System.Text.Json.Serialization.JsonIgnore

Web API - Converting JSON to Complex Object Type (DTO) with inheritance

I have the following scenario:
public class WidgetBaseDTO
{
public int WidgetID
{
get;
set;
}
}
public class WidgetTypeA : WidgetBaseDTO
{
public string SomeProperty1
{
get;
set;
}
}
public class WidgetTypeB : WidgetBaseDTO
{
public int SomeProperty2
{
get;
set;
}
}
and my web service returns the following dashboard object whereas the Widgets collection could be of either type A or B:
public class DashboardDTO
{
public List<WidgetBaseDTO> Widgets
{
get;
set;
}
}
my problem is that although the client receives correct JSON content, which is dependent on the Widget type, when reading the response content, they are all being translated to WidgetBaseDTO. what is the correct way to convert these objects to the relevant types?
this is how the response is being read:
string relativeRequestUri = string.Format("api/dashboards/GetDashboard?dashboardID={0}", dashboardID);
using (var client = new HttpClient())
{
// set client options
client.BaseAddress = this.BaseUri;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// make request
HttpResponseMessage response = client.GetAsync(relativeRequestUri).Result;
if (response.IsSuccessStatusCode)
{
DashboardDTO dashboard = response.Content.ReadAsAsync<DashboardDTO>().Result;
}
I believe after receiving the response you are probably trying to cast WidgetBaseDTO to either WidgetTypeA or WidgetTypeB and you are seeing null? if yes, then you can try after making the following setting to the Json formatter on the server...make sure to make this setting on the client side's json formatter too.
config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Objects;
The above setting will cause the type information of WidgetTypeA or WidgetTypeB to be put over the wire which gives a hint to the client as to the actual type of the object being deserialized...you can try looking at the wire format of the response to get an idea...
Client side:
JsonMediaTypeFormatter jsonFormatter = new JsonMediaTypeFormatter();
jsonFormatter.SerializerSettings.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Objects;
WidgetBaseDTO baseDTO = resp.Content.ReadAsAsync<WidgetBaseDTO>(new MediaTypeFormatter[] { jsonFormatter }).Result;

Json.net deseralize to a list of objects in c# .net 2.0

I'm trying to deseralize some json into a collection (list), but I'm not sure which method will return a list of objects, or do I have to loop through something and copy it to my own list?
Can anyone tell me the syntax or method I should use for this.
I've created my object with some properties, so it's ready to be used to hold the data. (title,url,description)
I've tried this, but it doesn't seem quite right
List<newsItem> test = (List<newsItem>)JsonConvert.DeserializeObject(Fulltext);
Did you try looking at the help?
http://james.newtonking.com/json/help/?topic=html/SerializingCollections.htm
string json = #"[
{
""Name"": ""Product 1"",
""ExpiryDate"": ""\/Date(978048000000)\/"",
""Price"": 99.95,
""Sizes"": null
},
{
""Name"": ""Product 2"",
""ExpiryDate"": ""\/Date(1248998400000)\/"",
""Price"": 12.50,
""Sizes"": null
}
]";
List<Product> products = JsonConvert.DeserializeObject<List<Product>>(json);
Console.WriteLine(products.Count);
// 2
Product p1 = products[0];
Console.WriteLine(p1.Name);
// Product 1
I'm using those extension methods:
public static string ToJSONArray<T>(this IEnumerable<T> list)
{
DataContractJsonSerializer s = new DataContractJsonSerializer(typeof(IEnumerable<T>));
MemoryStream ms = new MemoryStream();
s.WriteObject(ms, list);
return GetEncoder().GetString(ms.ToArray());
}
public static IEnumerable<T> FromJSONArray<T>(this string jsonArray)
{
if (string.IsNullOrEmpty(jsonArray)) return new List<T>();
DataContractJsonSerializer s = new DataContractJsonSerializer(typeof(IEnumerable<T>));
MemoryStream ms = new MemoryStream(GetEncoder().GetBytes(jsonArray));
var result = (IEnumerable<T>)s.ReadObject(ms);
if (result == null)
{
return new List<T>();
}
else
{
return result;
}
}
You need to decorate your Objects like this one:
[DataContract]
public class MyJSONObject
{
[DataMember]
public int IntValue { get; set; }
[DataMember]
public string StringValue { get; set; }
}
try using array instead of generic list. this may help.