I need to save an object's state into a file and retrieve it later. I found JSON serialization would help and found this method Json.gobject_serialize. Using this method, I can successfully serialize objects containing string properties. But what should I do, if the object A consists of another object (say B) within it and I need to serialize object A.
EDIT
What should I do if the object A consists of array (say B) of objects?
I created a small test program for this purpose and I failed in that try. I cannot find any detailed documentation about JSON Serialization for vala.
public class Foo : Object {
public int iFoo {get; set;}
public string sFoo {get; set;}
Bar[] _bar = {};
public Bar[] bar {get {return _bar;} set{_bar = value;}}
public class Bar : Object {
public int iBar {get; set;}
public string sBar {get; set;}
construct {
iBar = 02;
sBar = "OutOfRange";
}
}
construct {
_bar += new Bar();
iFoo = 74;
sFoo = "GIrafee";
}
public static int main () {
Json.Node root = Json.gobject_serialize (new Foo());
Json.Generator generator = new Json.Generator ();
generator.set_root (root);
stdout.printf(generator.to_data (null) + "\n");
return 0;
}
}
Serialization with JSON-GLib is recursive for properties containing complex types.
If the property of a GObject contains another GObject, json_gobject_serialize() will recursively call json_gobject_serialize() on the instance stored inside the property — or serialize the null if the property is unset.
I've implemented a object to support Json.Serializable interface as follow:
public class DbObject : GLib.Object, Json.Serializable
{
public Json.Object? meta { get; construct set; default = null; }
public VersionSync version { get; set; default = VersionSync.UNKNOWN; }
public virtual Value get_property (ParamSpec pspec)
{
Value prop_value = GLib.Value(pspec.value_type);
(this as GLib.Object).get_property(pspec.name, ref prop_value);
stdout.printf ("%s --> %s\n", prop_value.type_name(), prop_value.strdup_contents());
return prop_value;
}
public virtual void set_property (ParamSpec pspec, Value value)
{
(this as GLib.Object).set_property (pspec.name, value);
}
public unowned ParamSpec? find_property (string name)
{
return ((ObjectClass) get_type ().class_ref ()).find_property (name);
}
public virtual Json.Node serialize_property (string property_name, Value #value, ParamSpec pspec)
{
if (#value.type ().is_a (typeof (Json.Object)))
{
var obj = #value as Json.Object;
if (obj != null)
{
var node = new Json.Node (NodeType.OBJECT);
node.set_object (obj);
return node;
}
}
else if (#value.type ().is_a (typeof (Gee.ArrayList)))
{
unowned Gee.ArrayList<GLib.Object> list_value = #value as Gee.ArrayList<GLib.Object>;
if (list_value != null || property_name == "data")
{
var array = new Json.Array.sized (list_value.size);
foreach (var item in list_value)
{
array.add_element (gobject_serialize (item));
}
var node = new Json.Node (NodeType.ARRAY);
node.set_array (array);
return node;
}
}
else if (#value.type ().is_a (typeof (GLib.Array)))
{
unowned GLib.Array<GLib.Object> array_value = #value as GLib.Array<GLib.Object>;
if (array_value != null || property_name == "data")
{
var array = new Json.Array.sized (array_value.length);
for (int i = 0; i < array_value.length; i++) {
array.add_element (gobject_serialize (array_value.index(i)));
}
var node = new Json.Node (NodeType.ARRAY);
node.set_array (array);
return node;
}
}
else if (#value.type ().is_a (typeof (HashTable)))
{
var obj = new Json.Object ();
var ht_string = #value as HashTable<string, string>;
if (ht_string != null)
{
ht_string.foreach ((k, v) => {
obj.set_string_member (k, v);
});
var node = new Json.Node (NodeType.OBJECT);
node.set_object (obj);
return node;
} else {
var ht_object = #value as HashTable<string, GLib.Object>;
if (ht_object != null)
{
ht_object.foreach ((k, v) => {
obj.set_member (k, gobject_serialize (v));
});
var node = new Json.Node (NodeType.OBJECT);
node.set_object (obj);
return node;
}
}
}
return default_serialize_property (property_name, #value, pspec);
}
public virtual bool deserialize_property (string property_name, out Value #value, ParamSpec pspec, Json.Node property_node)
{
return default_deserialize_property (property_name, out #value, pspec, property_node);
}
}
Related
I am trying to serialize and deserialize my objects to save and load data.
I thought I was smart and introduced Attributes:
[ExposedProperty]
public float Width { get; set; }
[ExposedProperty]
public Color HoverColor { get; set; }
I have a PropertyData class:
[System.Serializable]
public class PropertyData
{
public string Name;
public string Type;
public object Value;
public override string ToString()
{
return "PropertyData ( Name = " + Name + ", Type = " + Type + ", Value = " + Value + ")";
}
}
So instead of writing an ObjectData class for every Object class I have that gets serialized into JSON, I though I'd write a Serializable class that does:
public List<PropertyData> SerializeProperties()
{
var list = new List<PropertyData>();
var type = this.GetType();
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty)
.Where(p => p.GetCustomAttributes(typeof(ExposedPropertyAttribute), false).Length > 0)
.ToArray();//
for (int i = 0; i < properties.Length; i++)
{
var property = properties[i];
var data = new PropertyData();
data.Name = property.Name;
data.Type = property.PropertyType.Name;
data.Value = property.GetValue(this);
list.Add(data);
}
return list;
}
and also to deserialize:
protected void DeserializePropertyData(PropertyData data)
{
var p = this.GetType().GetProperty(data.Name);
if (p == null)
{
Debug.LogError("Item " + this + " does not have a property with name '" + data.Name + "'");
return;
}
var type = p.PropertyType;
try
{
//TODO do some magic here to deserialize any of the values.
TypeConverter typeConverter = TypeDescriptor.GetConverter(type);
object propValue = typeConverter.ConvertFromString(data.Value);
p.SetValue(this, propValue);
}
catch(FormatException fe)
{
Debug.Log($"Serializable, there was a format exception on property {data.Name} and value {data.Value} for type {type}");
}
catch(NotSupportedException nse)
{
Debug.Log($"Serializable, there was a not supported exception on property {data.Name} and value {data.Value} for type {type}");
}
finally
{
}
}
But as it turns out, I can't serialize or deserialize Color, or Vector3, or Quaternion, or whatever. It only works for bool, float, string, int...
Any ideas how to serialize/deserialize other objects properly?
Thanks for all the answers. I looked into the ISerializationSurrogate Interface and noticed that it can't be used for UnityEngine.Vector3 or UnityEngine.Color classes.
Then I looked into TypeConverter and also saw that you can't directly use it because you have to add the attribute [TypeConverter(typeof(CustomTypeConverter))] on top of the very class or struct you want to convert.
And at the end of the day, both TypeConverter and ISerializationSurrogate are simply pushing and parsing chars around to get the result. For a Vector3, you have to trim the "(", ")", split the string with a "," and perform float.Parse on every element of the split string array. For a Color, you have to trim "RGBA(" and ")", and do the exact same thing.
Deadline is tight, so I took the same principle and created a static class that does my converting:
public static class MyCustomTypeConverter
{
public static object ConvertFromString(string value, Type destinationType)
{
if (destinationType == typeof(string))
{
return value;
}
else if (destinationType == typeof(int))
{
return int.Parse(value);
}
else if (destinationType == typeof(float))
{
return float.Parse(value);
}
else if (destinationType == typeof(double))
{
return double.Parse(value);
}
else if (destinationType == typeof(long))
{
return long.Parse(value);
}
else if (destinationType == typeof(bool))
{
return bool.Parse(value);
}
else if (destinationType == typeof(Vector3))
{
var mid = value.Substring(1, value.Length - 2);
var values = mid.Split(",");
return new Vector3(
float.Parse(values[0], CultureInfo.InvariantCulture),
float.Parse(values[1], CultureInfo.InvariantCulture),
float.Parse(values[2], CultureInfo.InvariantCulture)
);
}
else if (destinationType == typeof(Vector4))
{
var mid = value.Substring(1, value.Length - 2);
var values = mid.Split(",");
return new Vector4(
float.Parse(values[0], CultureInfo.InvariantCulture),
float.Parse(values[1], CultureInfo.InvariantCulture),
float.Parse(values[2], CultureInfo.InvariantCulture),
float.Parse(values[3], CultureInfo.InvariantCulture)
);
}
else if (destinationType == typeof(Color))
{
var mid = value.Substring(5, value.Length - 6);
var values = mid.Split(",");
return new Color(
float.Parse(values[0], CultureInfo.InvariantCulture),
float.Parse(values[1], CultureInfo.InvariantCulture),
float.Parse(values[2], CultureInfo.InvariantCulture),
float.Parse(values[3], CultureInfo.InvariantCulture)
);
}
else if (destinationType == typeof(PrimitiveType))
{
var e = Enum.Parse<PrimitiveType>(value);
return e;
}
else if (destinationType == typeof(SupportedLightType))
{
var e = Enum.Parse<SupportedLightType>(value);
return e;
}
return null;
}
public static string ConvertToString(object value)
{
return value.ToString();
}
}
It works, does what I want, and I can extend it for further classes or structs. Quaternion or Bounds perhaps, but I don't need much more than that.
My Serialization class now does this:
public List<PropertyData> SerializeProperties()
{
var list = new List<PropertyData>();
var type = this.GetType();
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty)
.Where(p => p.GetCustomAttributes(typeof(ExposedPropertyAttribute), false).Length > 0)
.ToArray();//
for (int i = 0; i < properties.Length; i++)
{
var property = properties[i];
var data = new PropertyData();
data.Name = property.Name;
data.Value = MyCustomTypeConverter.ConvertToString(property.GetValue(this));
list.Add(data);
}
return list;
}
and that:
protected void DeserializePropertyData(PropertyData data)
{
var p = this.GetType().GetProperty(data.Name);
if (p == null)
{
return;
}
var type = p.PropertyType;
object propValue = MyCustomTypeConverter.ConvertFromString(data.Value, type);
p.SetValue(this, propValue);
}
Suppose a JSON column called blob in a MySQL 5.7 database table called "Thing" with the following content:
[
{
"id": 1,
"value": "blue"
},
{
"id": 2,
"value": "red"
}
]
Is it possible to select all records from Thing where the blob contains an object within the array where the id is some dynamic value and the value is also some dynamic value.
E.g. "give me all Things where the blob contains an object whose id is 2 and value is 'red'"
Not sure how to form the WHERE clause below:
SET #id = 2;
SET #value1 = 'red';
SET #value2 = 'blue';
-- with equals?
SELECT *
FROM Thing
WHERE JSON_EXTRACT(blob, '$[*].id ... equals #id ... and .value') = #value1;
-- with an IN clause?
SELECT *
FROM Thing
WHERE JSON_EXTRACT(blob, '$[*].id ... equals #id ... and .value') IN (#value1, #value2);
They said it couldn't be done. They told me I was a fool. Lo and behold, I have done it!
Here are some helper functions to teach Hibernate how to perform JSON functions against a MySQL 5.7 backend, and an example use case. It's confusing, for sure, but it works.
Context
This contrived example is of a Person entity which can have many BioDetails, which is a question/answer type (but is more involved than that). The example within below essentially is searching within two JSON payloads, grabbing JSON values from one to build JSON paths within which to search in the other. In the end, you can pass in a complex structure of AND'd or OR'd criteria, which will be applied against a JSON blob and return only the resulting rows which match.
E.g. give my all Person entities where their age is > 30 and their favorite color is blue or orange. Given that those key/value pairs are stored in a JSON blob, you can find those matched using the example code below.
JSON-search classes and example repo
Classes below use Lombok for brevity.
Classes to allow specification of search criteria
SearchCriteriaContainer
#Data
#EqualsAndHashCode
public class SearchCriteriaContainer
{
private List<SearchCriterion> criteria;
private boolean and;
}
SearchCriterion
#Data
#EqualsAndHashCode(callSuper = true)
public class SearchCriterion extends SearchCriteriaContainer
{
private String field;
private List<String> values;
private SearchOperator operator;
private boolean not = false;
}
SearchOperator
#RequiredArgsConstructor
public enum SearchOperator
{
EQUAL("="),
LESS_THAN("<"),
LESS_THAN_OR_EQUAL("<="),
GREATER_THAN(">"),
GREATER_THAN_OR_EQUAL(">="),
LIKE("like"),
IN("in"),
IS_NULL("is null");
private final String value;
#JsonCreator
public static SearchOperator fromValue(#NotBlank String value)
{
return Stream
.of(SearchOperator.values())
.filter(o -> o.getValue().equals(value))
.findFirst()
.orElseThrow(() ->
{
String message = String.format("Could not find %s with value: %s", SearchOperator.class.getName(), value);
return new IllegalArgumentException(message);
});
}
#JsonValue
public String getValue()
{
return this.value;
}
#Override
public String toString()
{
return value;
}
}
Helper class which is used to call JSON functions
#RequiredArgsConstructor
public class CriteriaBuilderHelper
{
private final CriteriaBuilder criteriaBuilder;
public Expression<String> concat(Expression<?>... values)
{
return criteriaBuilder.function("CONCAT", String.class, values);
}
public Expression<String> substringIndex(Expression<?> value, String delimiter, int count)
{
return substringIndex(value, criteriaBuilder.literal(delimiter), criteriaBuilder.literal(count));
}
public Expression<String> substringIndex(Expression<?> value, Expression<String> delimiter, Expression<Integer> count)
{
return criteriaBuilder.function("SUBSTRING_INDEX", String.class, value, delimiter, count);
}
public Expression<String> jsonUnquote(Expression<?> jsonValue)
{
return criteriaBuilder.function("JSON_UNQUOTE", String.class, jsonValue);
}
public Expression<String> jsonExtract(Expression<?> jsonDoc, Expression<?> path)
{
return criteriaBuilder.function("JSON_EXTRACT", String.class, jsonDoc, path);
}
public Expression<String> jsonSearchOne(Expression<?> jsonDoc, Expression<?> value, Expression<?>... paths)
{
return jsonSearch(jsonDoc, "one", value, paths);
}
public Expression<String> jsonSearch(Expression<?> jsonDoc, Expression<?> value, Expression<?>... paths)
{
return jsonSearch(jsonDoc, "all", value, paths);
}
public Expression<String> jsonSearch(Expression<?> jsonDoc, String oneOrAll, Expression<?> value, Expression<?>... paths)
{
if (!"one".equals(oneOrAll) && !"all".equals(oneOrAll))
{
throw new RuntimeException("Parameter 'oneOrAll' must be 'one' or 'all', not: " + oneOrAll);
}
else
{
final var expressions = new ArrayList<>(List.of(
jsonDoc,
criteriaBuilder.literal(oneOrAll),
value,
criteriaBuilder.nullLiteral(String.class)));
if (paths != null)
{
expressions.addAll(Arrays.asList(paths));
}
return criteriaBuilder.function("JSON_SEARCH", String.class, expressions.toArray(Expression[]::new));
}
}
}
Utility to turn SearchCriteria into MySQL JSON function calls
SearchHelper
public class SearchHelper
{
private static final Pattern pathSeparatorPattern = Pattern.compile("\\.");
public static String getKeyPart(String key)
{
return pathSeparatorPattern.split(key)[0];
}
public static String getPathPart(String key)
{
final var parts = pathSeparatorPattern.split(key);
final var path = new StringBuilder();
for (var i = 1; i < parts.length; i++)
{
if (i > 1)
{
path.append(".");
}
path.append(parts[i]);
}
return path.toString();
}
public static Optional<Predicate> getCriteriaPredicate(SearchCriteriaContainer container, CriteriaBuilder cb, Path<String> bioDetailJson, Path<String> personJson)
{
final var predicates = new ArrayList<Predicate>();
if (container != null && container.getCriteria() != null && container.getCriteria().size() > 0)
{
final var h = new CriteriaBuilderHelper(cb);
container.getCriteria().forEach(ac ->
{
final var groupingOnly = ac.getField() == null && ac.getOperator() == null;
// a criterion can be used for grouping other criterion, and might not have a field/operator/value
if (!groupingOnly)
{
final var key = getKeyPart(ac.getField());
final var path = getPathPart(ac.getField());
final var bioDetailQuestionKeyPathEx = h.jsonUnquote(h.jsonSearchOne(bioDetailJson, cb.literal(key), cb.literal("$[*].key")));
final var bioDetailQuestionIdPathEx = h.concat(h.substringIndex(bioDetailQuestionKeyPathEx, ".", 1), cb.literal(".id"));
final var questionIdEx = h.jsonUnquote(h.jsonExtract(bioDetailJson, bioDetailQuestionIdPathEx));
final var answerPathEx = h.substringIndex(h.jsonUnquote(h.jsonSearchOne(personJson, questionIdEx, cb.literal("$[*].questionId"))), ".", 1);
final var answerValuePathEx = h.concat(answerPathEx, cb.literal("." + path));
final var answerValueEx = h.jsonUnquote(h.jsonExtract(personJson, answerValuePathEx));
switch (ac.getOperator())
{
case IN:
{
final var inEx = cb.in(answerValueEx);
if (ac.getValues() == null || ac.getValues().size() == 0)
{
throw new RuntimeException("No values provided for 'IN' criteria for field: " + ac.getField());
}
else
{
ac.getValues().forEach(inEx::value);
}
predicates.add(inEx);
break;
}
case IS_NULL:
{
predicates.add(cb.isNull(answerValueEx));
break;
}
default:
{
if (ac.getValues() == null || ac.getValues().size() == 0)
{
throw new RuntimeException("No values provided for '" + ac.getOperator() + "' criteria for field: " + ac.getField());
}
else
{
ac.getValues().forEach(value ->
{
final var valueEx = cb.literal(value);
switch (ac.getOperator())
{
case EQUAL:
{
predicates.add(cb.equal(answerValueEx, valueEx));
break;
}
case LESS_THAN:
{
predicates.add(cb.lessThan(answerValueEx, valueEx));
break;
}
case LESS_THAN_OR_EQUAL:
{
predicates.add(cb.lessThanOrEqualTo(answerValueEx, valueEx));
break;
}
case GREATER_THAN:
{
predicates.add(cb.greaterThan(answerValueEx, valueEx));
break;
}
case GREATER_THAN_OR_EQUAL:
{
predicates.add(cb.greaterThanOrEqualTo(answerValueEx, valueEx));
break;
}
case LIKE:
{
predicates.add(cb.like(answerValueEx, valueEx));
break;
}
default:
throw new RuntimeException("Unsupported operator during snapshot search: " + ac.getOperator());
}
});
}
}
}
}
// iterate nested criteria
getAnswerCriteriaPredicate(ac, cb, bioDetailJson, personJson).ifPresent(predicates::add);
});
return Optional.of(container.isAnd()
? cb.and(predicates.toArray(Predicate[]::new))
: cb.or(predicates.toArray(Predicate[]::new)));
}
else
{
return Optional.empty();
}
}
}
Example JPA Specification repository / search method
ExampleRepository
#Repository
public interface PersonRepository extends JpaSpecificationExecutor<Person>
{
default Page<Person> search(PersonSearchDirective directive, Pageable pageable)
{
return findAll((person, query, cb) ->
{
final var bioDetail = person.join(Person_.bioDetail);
final var bioDetailJson = bioDetail.get(BioDetailEntity_.bioDetailJson);
final var personJson = person.get(Person_.personJson);
final var predicates = new ArrayList<>();
SearchHelper
.getCriteriaPredicate(directive.getSearchCriteria(), cb, bioDetailJson, personJson)
.ifPresent(predicates::add);
return cb.and(predicates.toArray(Predicate[]::new));
}, pageable);
}
}
I have the following code to get my cshtml Email Template.
try
{
var layoutTemplateKey = new NameOnlyTemplateKey("layout", ResolveType.Global, null);
if (!Engine.Razor.IsTemplateCached(layoutTemplateKey, null))
Engine.Razor.AddTemplate(layoutTemplateKey, ReadTemplateFile("/Views/Email/Layout.cshtml"));
var templateKey = new NameOnlyTemplateKey(templatePath, ResolveType.Global, null);
if (!Engine.Razor.IsTemplateCached(templateKey, viewModel.GetType()))
{
Engine.Razor.AddTemplate(templateKey, ReadTemplateFile(templatePath));
Engine.Razor.Compile(templateKey, viewModel.GetType());
}
return viewModel == null ?
Engine.Razor.Run(templateKey) :
Engine.Razor.Run(templateKey, viewModel.GetType(), viewModel);
}
catch (Exception ex)
{
return ex.Message;
}
However My the Engine.Razor.Run causes InvalidCastException with a model which is no where referenced here.
And it continuously happens with any other models too.
The recent change I was done was inheriting each of this class by another Model to be used in the Layout.cshtml.
Expected Model: NewBookRecievedEmailViewModel
public class NewBookRecievedEmailViewModel : EmailHelperModel
{
public ProposalPostedEmailViewModel()
{
this.canBeDeleted = true;
}
public string BookName { get; set; }
public string Author {get; set;}
}
Gets error with Model PayOutBookEmailViewModel
public class PayOutBookEmailViewModel : EmailHelperModel
{
public string BookID { get; set; }
public string BookUrl {get; set;}
}
And the inherited Model is : EmailHelper
public class EmailHelperModel
{
public bool canBeDeleted { get; set; }
public string DeleteUrl { get; set; }
}
Stack Trace:
at RazorEngine.Templating.TemplateBase`1.SetModel(Object model)
at RazorEngine.Templating.TemplateBase.SetData(Object model, DynamicViewBag viewbag)
at RazorEngine.Templating.RazorEngineCore.CreateTemplate(ICompiledTemplate template, Object model, DynamicViewBag viewbag)
at RazorEngine.Templating.RazorEngineCoreWithCache.ResolveInternal(String cacheName, Object model, Type modelType, DynamicViewBag viewbag, ResolveType resolveType, ITemplateKey context)
at RazorEngine.Templating.InternalTemplateService.Resolve(String name, Object model, Type modelType, DynamicViewBag viewbag, ResolveType resolveType)
at RazorEngine.Templating.TemplateBase`1.ResolveLayout(String name)
at RazorEngine.Templating.TemplateBase.RazorEngine.Templating.ITemplate.Run(ExecuteContext context, TextWriter reader)
at RazorEngine.Templating.RazorEngineCore.RunTemplate(ICompiledTemplate template, TextWriter writer, Object model, DynamicViewBag viewBag)
at RazorEngine.Templating.RazorEngineService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
at RazorEngine.Templating.DynamicWrapperService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass22_0.<Run>b__0(TextWriter writer)
at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter)
at RazorEngine.Templating.RazorEngineServiceExtensions.Run(IRazorEngineService service, ITemplateKey key, Type modelType, Object model, DynamicViewBag viewBag)
at Pxp.Api.Helpers.EmailHelper.GetHtmlContent(String templatePath, Object viewModel)
Instead of using cshtml, consider using actual HTML; or better yet, generating elements with JavaScript, it's the real way-to-go, here' some sample code of how to create an element in JavaScript:
var MyEngine = new (function() {
this.el = function(opts) {
if(!svgList.split(" ").find(x => x == opts.tag)) {
this.node = document.createElement(opts.tag || "div");
} else {
this.node = document.createElementNS('http://www.w3.org/2000/svg', opts.tag);
}
for(var k in opts) {
if(k == "style") {
for(var s in opts[k]) {
this.node.style[s] = opts[k][s];
}
} else if(k != "parent"){
this.node[k] = opts[k];
}
}
this.setAttrs = (attrs) => {
for(var k in attrs) {
this.node.setAttribute(k, attrs[k]);
}
};
this.getAttr = (at) => {
return this.node.getAttribute(at);
};
this.setStyle = (stl) => {
for(var k in stl) {
this.node.style[k] = stl[k];
}
}
var attr = opts.attr || {};
this.setAttrs(attr);
var optsPar = opts.parent;
var par = null;
if(optsPar) {
if(optsPar.constructor == String) {
par = f("#" + optsPar);
} else if(optsPar instanceof Element) {
par = optsPar;
}
}
this.parent = par || document.body || {appendChild: (d) => {}};
this.parent.appendChild(this.node);
};
)();
then to create a new element:
var temp = new MyEngine.el({
id:"hi",
style:{position:absolute, left:100+"px"},
innerHTML:"<b>nice to meet you</b>
});
I am trying to find the IndexOf in a list using a value as reference. Like lets say Id = 23144, I want the index when entering the Id.
This Object list is made by deserializing a Json retrieved with RestSharp.
{
"not_modified_since":"2018-06-05T22:22:18Z",
"host":[
{
"active":true,
"config_profile_bag_id":0,
"container_id":0,
"db_pickup_tm_utc":"2018-01-11T10:12:55",
"discovery_status":0,
"display_unit_id":0,
"domain_id":103947039,
"geolocation":"(0,0)",
"id":195392183,
"license_end_date":null,
"licensed":true,
"name":"Broadsign Services - Mathias - 16x64",
"nscreens":0,
"primary_mac_address":"00:0c:29:e0:e6:22",
"public_key_fingerprint":"Redacted",
"remote_clear_db_tm_utc":"1970-01-01T00:00:00",
"remote_reboot_tm_utc":"2017-12-12T10:17:23",
"secondary_mac_address":"",
"volume":-1
}
]
}
I have the following code:
private void bRun_Click(object sender, EventArgs e)
{
var client = new RestClient(endPoint);
var request = new RestRequest("/host/v14/by_id", Method.GET);
request.AddHeader("accept", "application/json");
request.AddHeader("Authorization", "Bearer " + key);
request.AddParameter("domain_id", "103947039");
request.AddParameter("ids", "195392183");
request.OnBeforeDeserialization = resp => { resp.ContentType = "application/json"; };
request.RequestFormat = DataFormat.Json;
var response = client.Execute<RootObject>(request);
var rootObject = JsonConvert.DeserializeObject<RootObject>(response.Content);
var hosts = rootObject.Host;
oResponse.Text = hosts.IndexOf();
}
There could be multiple host, so I'd like to be able to find the index of each host so I can then know which one to retrieve the value of following certain conditions, for example those that match the Id. In Sum I'd like to be able to know that id 195392183 is at index 0 so that I can then use that index to retrieve the value for name ( Broadsign Services - Mathias - 16x64 ) for example.
EDIT!
SOLVED! This worked for me:
var index = rootObject.Host.FindIndex(host => host.Id == 195392183);
You can create a dictionary based on the Host
var hostsDict = rootObject.Select(r=> r.Host).ToDictionary<int, Host>(k=> k.Id, v=> v);
return hostDict[195392183];
You could add an index to your deserialized object using a custom coverter.
public class ListWithIndexConverter<T> : JsonConverter where T : IIndexedObject
{
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 new List<T>();
}
var returnList = new List<T>();
for(var i=0; i < token.Count(); i++)
{
var returnObject = token[i].ToObject<T>();
returnObject.Index = i;
returnList.Add(returnObject);
}
return returnList;
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public interface IIndexedObject
{
int Index { get; set; }
}
public class SomeObject
{
[JsonConverter(typeof(ListWithIndexConverter<Host>))]
public List<Host> Hosts { get; set; }
}
public class Host : IIndexedObject
{
//some properties
}
I have a situation in which I have a very large C# object, however, I only need to return a handful of properties (which can be on nested objects), allow for client-side JavaScript to modify those properties and then send the resulting object back to the server in order to perform in-place partial de-serialization.
The idea is to re-use some very large existing business objects, but be intelligent about only serializing and sending only those properties back to the client application for modification (to keep the amount of data transferred at a minimum).
I basically have an XML file where I pre-define all of the bindings using a "path syntax" which would indicate only those properties I need to serialize. So, I could use something like "WorkOrder.UserField1" or "WorkOrder.Client.Name".
I have tried using a custom contract resolver to determine whether or not a property should be serialized; however, it doesn't seem that I have information as to the "path" (in other words, other properties in the object model up the chain) in order to determine if the property should or should not be serialized.
I have also tried using a custom JsonTextWriter, but it doesn't seem that I can override the methods necessary to keep track of the path, even though there is a Path property available. Is there something perhaps simple that I am overlooking in order to be able to view the path hierarchy of a property being serialized and determine if it should be serialized by looking up the path in a table and making the decision?
The basic difficulty here is that Json.NET is a contract-based serializer which creates a contract for each type to be serialized, then (de)serializes according to the contract. If a type appears in multiple locations in the object hierarchy, the same contract applies. But you want to selectively include properties for a given type depending on its location in the hierarchy, which conflicts with the basic "one type one contract" design.
One quick way to work around this is to serialize to a JObject, then use JToken.SelectTokens() to select only the JSON data you want to return, removing everything else. Since SelectTokens has full support for JSONPath query syntax, you can selectively include using array and property wildcards or other filters, for instance:
"$.FirstLevel[*].Bar"
includes all properties named "Bar" in all array members of a property named "FirstLevel" of the root object.
This should reduce your network usage as desired, but won't save any processing time on the server.
Removal can be accomplished with the following extension methods:
public static partial class JsonExtensions
{
public static TJToken RemoveAllExcept<TJToken>(this TJToken obj, IEnumerable<string> paths) where TJToken : JToken
{
if (obj == null || paths == null)
throw new NullReferenceException();
var keepers = new HashSet<JToken>(paths.SelectMany(path => obj.SelectTokens(path)), ObjectReferenceEqualityComparer<JToken>.Default);
var keepersAndParents = new HashSet<JToken>(keepers.SelectMany(t => t.AncestorsAndSelf()), ObjectReferenceEqualityComparer<JToken>.Default);
// Keep any token that is a keeper, or a child of a keeper, or a parent of a keeper
// I.e. if you have a path ""$.A.B" and it turns out that B is an object, then everything
// under B should be kept.
foreach (var token in obj.DescendantsAndSelfReversed().Where(t => !keepersAndParents.Contains(t) && !t.AncestorsAndSelf().Any(p => keepers.Contains(p))))
token.RemoveFromLowestPossibleParent();
// Return the object itself for fluent style programming.
return obj;
}
public static string SerializeAndSelectTokens<T>(T root, string[] paths, Formatting formatting = Formatting.None, JsonSerializerSettings settings = null)
{
var obj = JObject.FromObject(root, JsonSerializer.CreateDefault(settings));
obj.RemoveAllExcept(paths);
var json = obj.ToString(formatting);
return json;
}
public static TJToken RemoveFromLowestPossibleParent<TJToken>(this TJToken node) where TJToken : JToken
{
if (node == null)
return null;
JToken toRemove;
var property = node.Parent as JProperty;
if (property != null)
{
// Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
toRemove = property;
property.Value = null;
}
else
{
toRemove = node;
}
if (toRemove.Parent != null)
toRemove.Remove();
return node;
}
public static IEnumerable<JToken> DescendantsAndSelfReversed(this JToken node)
{
if (node == null)
throw new ArgumentNullException();
return RecursiveEnumerableExtensions.Traverse(node, t => ListReversed(t as JContainer));
}
// Iterate backwards through a list without throwing an exception if the list is modified.
static IEnumerable<T> ListReversed<T>(this IList<T> list)
{
if (list == null)
yield break;
for (int i = list.Count - 1; i >= 0; i--)
yield return list[i];
}
}
public static partial class RecursiveEnumerableExtensions
{
// Rewritten from the answer by Eric Lippert https://stackoverflow.com/users/88656/eric-lippert
// to "Efficient graph traversal with LINQ - eliminating recursion" http://stackoverflow.com/questions/10253161/efficient-graph-traversal-with-linq-eliminating-recursion
// to ensure items are returned in the order they are encountered.
public static IEnumerable<T> Traverse<T>(
T root,
Func<T, IEnumerable<T>> children)
{
yield return root;
var stack = new Stack<IEnumerator<T>>();
try
{
stack.Push((children(root) ?? Enumerable.Empty<T>()).GetEnumerator());
while (stack.Count != 0)
{
var enumerator = stack.Peek();
if (!enumerator.MoveNext())
{
stack.Pop();
enumerator.Dispose();
}
else
{
yield return enumerator.Current;
stack.Push((children(enumerator.Current) ?? Enumerable.Empty<T>()).GetEnumerator());
}
}
}
finally
{
foreach (var enumerator in stack)
enumerator.Dispose();
}
}
}
/// <summary>
/// A generic object comparerer that would only use object's reference,
/// ignoring any <see cref="IEquatable{T}"/> or <see cref="object.Equals(object)"/> overrides.
/// </summary>
public class ObjectReferenceEqualityComparer<T> : IEqualityComparer<T> where T : class
{
// Adapted from this answer https://stackoverflow.com/a/1890230
// to https://stackoverflow.com/questions/1890058/iequalitycomparert-that-uses-referenceequals
// By https://stackoverflow.com/users/177275/yurik
private static readonly IEqualityComparer<T> _defaultComparer;
static ObjectReferenceEqualityComparer() { _defaultComparer = new ObjectReferenceEqualityComparer<T>(); }
public static IEqualityComparer<T> Default { get { return _defaultComparer; } }
#region IEqualityComparer<T> Members
public bool Equals(T x, T y)
{
return ReferenceEquals(x, y);
}
public int GetHashCode(T obj)
{
return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj);
}
#endregion
}
And then use them like:
public class TestClass
{
public static void Test()
{
var root = new RootObject
{
FirstLevel1 = new FirstLevel
{
SecondLevel1 = new List<SecondLevel> { new SecondLevel { A = "a11", B = "b11", Third1 = new ThirdLevel { Foo = "Foos11", Bar = "Bars11" }, Third2 = new List<ThirdLevel> { new ThirdLevel { Foo = "FooList11", Bar = "BarList11" } } } },
SecondLevel2 = new List<SecondLevel> { new SecondLevel { A = "a12", B = "b12", Third1 = new ThirdLevel { Foo = "Foos12", Bar = "Bars12" }, Third2 = new List<ThirdLevel> { new ThirdLevel { Foo = "FooList12", Bar = "BarList12" } } } },
},
FirstLevel2 = new FirstLevel
{
SecondLevel1 = new List<SecondLevel> { new SecondLevel { A = "a21", B = "b21", Third1 = new ThirdLevel { Foo = "Foos21", Bar = "Bars21" }, Third2 = new List<ThirdLevel> { new ThirdLevel { Foo = "FooList21", Bar = "BarList21" } } } },
SecondLevel2 = new List<SecondLevel> { new SecondLevel { A = "a22", B = "b22", Third1 = new ThirdLevel { Foo = "Foos22", Bar = "Bars22" }, Third2 = new List<ThirdLevel> { new ThirdLevel { Foo = "FooList22", Bar = "BarList22" } } } },
}
};
Assert.IsTrue(JObject.FromObject(root).DescendantsAndSelf().OfType<JValue>().Count() == 24); // No assert
var paths1 = new string[]
{
"$.FirstLevel2.SecondLevel1[*].A",
"$.FirstLevel1.SecondLevel2[*].Third2[*].Bar",
};
Test(root, paths1, 2);
var paths3 = new string[]
{
"$.FirstLevel1.SecondLevel2[*].Third2[*].Bar",
};
Test(root, paths3, 1);
var paths4 = new string[]
{
"$.*.SecondLevel2[*].Third2[*].Bar",
};
Test(root, paths4, 2);
}
static void Test<T>(T root, string [] paths, int expectedCount)
{
var json = JsonExtensions.SerializeAndSelectTokens(root, paths, Formatting.Indented);
Console.WriteLine("Result using paths: {0}", JsonConvert.SerializeObject(paths));
Console.WriteLine(json);
Assert.IsTrue(JObject.Parse(json).DescendantsAndSelf().OfType<JValue>().Count() == expectedCount); // No assert
}
}
public class ThirdLevel
{
public string Foo { get; set; }
public string Bar { get; set; }
}
public class SecondLevel
{
public ThirdLevel Third1 { get; set; }
public List<ThirdLevel> Third2 { get; set; }
public string A { get; set; }
public string B { get; set; }
}
public class FirstLevel
{
public List<SecondLevel> SecondLevel1 { get; set; }
public List<SecondLevel> SecondLevel2 { get; set; }
}
public class RootObject
{
public FirstLevel FirstLevel1 { get; set; }
public FirstLevel FirstLevel2 { get; set; }
}
Note that there is an enhancement request Feature request: ADD JsonProperty.ShouldSerialize(object target, string path) #1857 that would enable this sort of functionality more easily.
Demo fiddles here and here.
The much easier implementation (comparing to the accepted answer) is presented here:
public static class JsonExtensions
{
public static TJToken RemoveAllExcept<TJToken>(this TJToken token, IEnumerable<string> paths) where TJToken : JContainer
{
HashSet<JToken> nodesToRemove = new(ReferenceEqualityComparer.Instance);
HashSet<JToken> nodesToKeep = new(ReferenceEqualityComparer.Instance);
foreach (var whitelistedToken in paths.SelectMany(token.SelectTokens))
TraverseTokenPath(whitelistedToken, nodesToRemove, nodesToKeep);
//In that case neither path from paths has returned any token
if (nodesToKeep.Count == 0)
{
token.RemoveAll();
return token;
}
nodesToRemove.ExceptWith(nodesToKeep);
foreach (var notWhitelistedNode in nodesToRemove)
notWhitelistedNode.Remove();
return token;
}
private static void TraverseTokenPath(JToken value, ISet<JToken> nodesToRemove, ISet<JToken> nodesToKeep)
{
JToken? immediateValue = value;
do
{
nodesToKeep.Add(immediateValue);
if (immediateValue.Parent is JObject or JArray)
{
foreach (var child in immediateValue.Parent.Children())
if (!ReferenceEqualityComparer.Instance.Equals(child, value))
nodesToRemove.Add(child);
}
immediateValue = immediateValue.Parent;
} while (immediateValue != null);
}
}
For most cases this can be achieved by a simple single line extension method
public static string ToJson<T>(this T self, string path) => $#"{{""{path}"":{JObject.FromObject(self)[path]?.ToString(Formatting.None)}}}";
This is only valid for extracting an object nested under the root object but is easily adapted with a separate parameter to specify the output path if needed
Thanks to #dbc answer as a good solution, but like he said, it doesn't affect the performance. Sometimes the data loaded from database has numerous references and only ignoring ReferenceLoopHandling is not enough for serialization; hence the serialized data becomes very large and takes a lot of ram in server, and this is caused by repetition of serializing a single object. In this situation, it's better to make a limited jobject from data straightly, rather than making a jobject and then exclude the unwanted paths from it. This can be done with a little customization of database pure data and a ContractResolver. Let's assume all the database entities inherit from a class or interface like DbModel (this is necessary in this solution). Then by a special ContractResolver, serialization of objects can be limited. A sample is like below:
class TypeName
{
public Type Type { get; set; }
public string Name { get; set; }
}
class MyContractResolver : DefaultContractResolver
{
private List<List<TypeName>> allTypeNames = new List<List<TypeName>>();
public MyContractResolver(Type parentType, string[] includePaths)
{
foreach (var includePath in includePaths)
{
List<TypeName> typeNames = new List<TypeName>() { new TypeName() { Type = parentType } };
var pathChilderen = includePath.Split('.');
for(int i = 0; i < pathChilderen.Length; i++)
{
var propType = typeNames[i].Type.GetProperties().FirstOrDefault(c => c.Name == pathChilderen[i]).PropertyType;
if (propType.GetInterface(nameof(IEnumerable)) != null && propType != typeof(String))
{
propType = propType.GetGenericArguments().Single();
}
typeNames.Add(new TypeName() { Name = pathChilderen[i], Type = propType });
}
allTypeNames.Add(typeNames);
}
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);
// only serializer properties that are in include paths
List<JsonProperty> excludeProperties = new List<JsonProperty>();
foreach (var property in properties)
{
if (typeof(DbModel).IsAssignableFrom(property.PropertyType) || (property.PropertyType.GetInterface(nameof(IEnumerable)) != null && property.PropertyType != typeof(String)))
{
Console.WriteLine(property.PropertyType.ToString());
var exclude = true;
foreach (var typeNames in allTypeNames)
{
var index = typeNames.FindIndex(c => c.Name == property.PropertyName && c.Type == property.PropertyType);
if (index > 0)
{
if (typeNames[index - 1].Type == type)
{
exclude = false;
goto EndSearch;
}
}
}
EndSearch:
if (exclude)
excludeProperties.Add(property);
}
}
properties = properties.Where(c => excludeProperties.All(d => d.PropertyName != c.PropertyName)).ToList();
return properties;
}
}
This class can be used like this:
// return Ok(data);
var jObject = JObject.FromObject(data,
JsonSerializer.CreateDefault(new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
Converters = new List<JsonConverter>()
{
new ValidationProblemDetailsConverter(),
new ProblemDetailsConverter(),
new StringEnumConverter()
},
ContractResolver = new MyContractResolver(typeof(Foo), new[] { "bar", "baz.qux" })
}));
return Ok(jObject);
In this example Foo is the class of main object to return, and bar and baz are properties that are going to be serialized (they are loaded from database too). In addition qux is one of the baz properties that is loaded from database and has to be serialized. In this example all the other properties of each model that are not entities of database (so are not inherited from DbModel) are serialized and all the entities of database that exist in original data but not in the including paths, are ignored to be serialized.