I would like to know how I can use JAXB to consume and produce a JSON array from a List without being included in an object:
[ "element1", "element2", "element3"]
Currently I have being only able to do so by including it in an object:
{ "elements" : [ "element1", "element2", "element3"] }
The code used is more or less the one below.
#XmlRootElement(name = "elements")
public class ElementListSerializerHelper
#XmlElement(name = "elements")
#JsonProperty(value = "elements")
public List<String> list;
public ElementsListSerializerHelper() {
list = new ArrayList<String>();
}
public ElementsListSerializerHelper(List<String> list) {
this.list = new ArrayList<String>(list);
}
}
Any help is more than welcome. The option I'm also thinking is generating/parsing a string based on jackson library.
Related
There are examples out there of custom MVC validators that take an array parameter, but only server side - none of them show an example of implementing the client side with array parameter.
The problem is instead of outputting the array's contents in the html data- attribute, it outputs "System.String[]":
data-val-total-propertynames="System.String[]"
Here is my attribute class:
public class TotalAttribute : ValidationAttribute, IClientValidatable
{
private String[] PropertyNames { get; set; }
public TotalAttribute(String[] propertyNames)
{
PropertyNames = propertyNames;
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
float total = 0;
foreach (var propertyName in PropertyNames)
total += (float)context.ObjectInstance.GetType().GetProperty(propertyName).GetValue(context.ObjectInstance, null);
if (total != (float)value)
return new ValidationResult(FormatErrorMessage(context.DisplayName), new[] { context.MemberName });
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = ErrorMessageString,
ValidationType = "total",
};
rule.ValidationParameters["propertynames"] = PropertyNames;
yield return rule;
}
}
Here it is implemented in the model:
[Total(new string[] { "SomeOtherField1", "SomeOtherField2" }, ErrorMessage = "'Line12Balance' must equal total of 'SomeOtherField1' and 'SomeOtherField2'")]
public decimal? Line12Balance { get; set; }
Here is the html data-val attribute output:
data-val-total-propertynames="System.String[]"
What am I doing wrong?
You get "System.String[]" string because the value of ValidationParameters["propertynames"] is written by calling ToString on it, so string[] variable returns "System.String[]" in this case. If you need to output specific value you need to format it by yourself in your validation attribute. For example, change
rule.ValidationParameters["propertynames"] = PropertyNames;
to
rule.ValidationParameters["propertynames"] = string.Join(",", PropertyNames);
and you will get
data-val-total-propertynames="SomeOtherField1,SomeOtherField2"
As Alexander mentioned in his answer, the issue you encountered is that string[] doesn't have its own ToString() implementation, and uses the base object.ToString() implementation, which just displays the type of the object rather than the contents of your array.
So you've got to somehow serialize your string array so that it can be stored in a string to then parse in your client-side JavaScript.
If you're already using JSON.Net in your solution (as many are), you can also do this by JSON serializing the array on the server side, then parsing the JSON client-side.
i.e. server-side:
rule.ValidationParameters["propertynames"] = JsonConvert.SerializeObject(PropertyNames);
Client-side: (
$.validator.addMethod('rule', function(value, element, params) {
var propertynames = JSON.parse(params.propertynames);
// do your validation
});
That prevents you from having to escape commas/delimiters in your parameters.
I am having to parse (and ultimately reserialize) some dodgy JSON. it looks like this:
{
name: "xyz",
id: "29573f59-85fb-4d06-9905-01a3acb2cdbd",
status: "astatus",
color: colors["Open"]
},
{
name: "abc",
id: "29573f59-85fb-4d06-9905-01a3acb2cdbd",
status: "astatus",
color: colors["Open"]
}
There are a number of problems here - starting with the most severe.
color: colors["Open"]
WTF even is that? If I drop 'colors' then I can get an array of strings out but I can't tweak to work out of the box.
It is an array without square brackets. I can fix this by wrapping in them. But is there a way to support out of the box?
Properties have no quotes. Deserializing is fine for these.. but reserializing is just no dice.
Any suggestions of handling both in and out of this structure?
Answering your questions #1 - #3 in order:
Json.NET does not support reading dodgy property values in the form colors["Open"] (which, as you correctly note, violates the JSON standard).
Instead, you will need to manually fix these values, e.g. through some sort of Regex:
var regex = new Regex(#"(colors\[)(.*)(\])");
var fixedJsonString = regex.Replace(jsonString,
m => string.Format(#"""{0}{1}{2}""", m.Groups[1].Value, m.Groups[2].Value.Replace("\"", "\\\""), m.Groups[3].Value));
This changes the color property values into properly escaped JSON strings:
color: "colors[\"Open\"]"
Json.NET does, however, have the capability to write dodgy property values by calling JsonWriter.WriteRawValue() from within a custom JsonConverter.
Define the following converter:
public class RawStringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override bool CanRead { get { return false; } }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var s = (string)value;
writer.WriteRawValue(s);
}
}
Then define your RootObject as follows:
public class RootObject
{
public string name { get; set; }
public string id { get; set; }
public string status { get; set; }
[JsonConverter(typeof(RawStringConverter))]
public string color { get; set; }
}
Then, when re-serialized, you will get the original dodgy values in your JSON.
Support for deserializing comma-delimited JSON without outer brackets will be in the next release of Json.NET after 10.0.3. see Issue 1396 and Issue 1355 for details. You will need to set JsonTextReader.SupportMultipleContent = true to make it work.
In the meantime, as a workaround, you could grab ChainedTextReader and public static TextReader Extensions.Concat(this TextReader first, TextReader second) from the answer to How to string multiple TextReaders together? by Rex M and surround your JSON with brackets [ and ].
Thus you would deserialize your JSON as follows:
List<RootObject> list;
using (var reader = new StringReader("[").Concat(new StringReader(fixedJsonString)).Concat(new StringReader("]")))
using (var jsonReader = new JsonTextReader(reader))
{
list = JsonSerializer.CreateDefault().Deserialize<List<RootObject>>(jsonReader);
}
(Or you could just manually surround your JSON string with [ and ], but I prefer solutions that don't involve copying possibly large strings.)
Re-serializing a root collection without outer braces is possible if you serialize each item individually using its own JsonTextWriter with CloseOutput = false. You can also manually write a , between each serialized item to the underlying TextWriter shared by every JsonTextWriter.
Serializing JSON property names without a surrounding quote character is possible if you set JsonTextWriter.QuoteName = false.
Thus, to re-serialize your List<RootObject> without quoted property names or outer braces, do:
var sb = new StringBuilder();
bool first = true;
using (var textWriter = new StringWriter(sb))
{
foreach (var item in list)
{
if (!first)
{
textWriter.WriteLine(",");
}
first = false;
using (var jsonWriter = new JsonTextWriter(textWriter) { QuoteName = false, Formatting = Formatting.Indented, CloseOutput = false })
{
JsonSerializer.CreateDefault().Serialize(jsonWriter, item);
}
}
}
var reserializedJson = sb.ToString();
Sample .Net fiddle showing all this in action.
This question already has answers here:
Serialize and Deserialize Json and Json Array in Unity
(9 answers)
Closed 5 years ago.
I need to save multiple player's data. I am doing it by making an array of PlayersInfo class and trying to convert the array into JSON. here is my code
PlayerInfo[] allPlayersArray = new PlayerInfo[1];
allPlayersArray[0] = new PlayerInfo();
allPlayersArray[0].playerName = "name 0";
string allPlayersArrayJson = JsonUtility.ToJson(allPlayersArray);
print(allPlayersArrayJson);
PlayerPrefs.SetString("allPlayersArrayJson", allPlayersArrayJson);
string newJson = PlayerPrefs.GetString("allPlayersArrayJson");
print(newJson);
PlayerInfo[] newArray = new PlayerInfo[1];
newArray = JsonUtility.FromJson<PlayerInfo[]>(newJson);
print(newArray[0].playerName);
First two print statements returns "{}" and 3rd one gives null reference error. TIA
Like I said in my comment, there is no direct support. Helper class is needed. This is only reason I am making this answer is because you are still having problems even after reading the link I provided.
Create a new script called JsonHelper. Copy and paste the code below inside it.
using UnityEngine;
using System.Collections;
using System;
public class JsonHelper
{
public static T[] FromJson<T>(string json)
{
Wrapper<T> wrapper = UnityEngine.JsonUtility.FromJson<Wrapper<T>>(json);
return wrapper.Items;
}
public static string ToJson<T>(T[] array)
{
Wrapper<T> wrapper = new Wrapper<T>();
wrapper.Items = array;
return UnityEngine.JsonUtility.ToJson(wrapper);
}
[Serializable]
private class Wrapper<T>
{
public T[] Items;
}
}
The code in your question should now work. All you have to do is to replace all JsonUtility words with JsonHelper. I did that for you below:
void Start()
{
PlayerInfo[] allPlayersArray = new PlayerInfo[1];
allPlayersArray[0] = new PlayerInfo();
allPlayersArray[0].playerName = "name 0";
string allPlayersArrayJson = JsonHelper.ToJson(allPlayersArray);
print(allPlayersArrayJson);
PlayerPrefs.SetString("allPlayersArrayJson", allPlayersArrayJson);
string newJson = PlayerPrefs.GetString("allPlayersArrayJson");
print(newJson);
PlayerInfo[] newArray = new PlayerInfo[1];
newArray = JsonHelper.FromJson<PlayerInfo>(newJson);
print(newArray[0].playerName);
}
Based on the JsonUtility documentation, naked arrays are not supported. Put the array inside a class.
Internally, this method uses the Unity serializer; therefore the
object you pass in must be supported by the serializer: it must be a
MonoBehaviour, ScriptableObject, or plain class/struct with the
Serializable attribute applied.
In general, you'll use this to serialize MonoBehaviour objects, or a custom class/struct with the Serializable attribute.
YES! It is not supported currently but there is always a work around. Use this class :
public static class JsonHelper
{
public static T[] getJsonArray<T>(string json)
{
try
{
string newJson = "{ \"array\": " + json + "}";
Wrapper<T> wrapper = JsonUtility.FromJson<Wrapper<T>> (newJson);
return wrapper.array;
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
[Serializable]
private class Wrapper<T>
{
public T[] array = null;
}
}
I would use Json.NET
PlayerInfo[] allPlayersArray = new PlayerInfo[] { p1, p2, p3 };
string json = JsonConvert.SerializeObject(allPlayersArray);
More On SerializeObject
(Code example not yet tested)
I want to store some data from an HTML form (working with AngularJS) into my database, using Spring.
For this, I'm using the #RequestBody annotation with a POJO, but I can't make it work: my POJO is instancied, but it looks like the POJO attributes are not mapped with my form values (they are all null).
Controller :
#RequestMapping(value = "/createEntities", method = RequestMethod.POST)
#ResponseBody
public List<Entity> createEntities(#RequestBody final EntityList resource, #RequestParam final String kind) {
System.out.println("Creating entity for: " + kind);
Preconditions.checkNotNull(resource);
List<Entity> newEntities = new ArrayList<Entity>();
System.out.println("Entity test = " + resource.getTest()); // Prints "Entity test = null"
// Code below returns NullException
//System.out.println("Entity list nb = " + resource.getEntity().size());
if (resource.getEntities() != null && !resource.getEntities().isEmpty()) {
System.out.println("Entity list is OK");
for (EntityForm eForm : resource.getEntities()) {
if (eForm.getGrant() != null) {
Entity ent = new Entity();
if ("RTS".equals(kind)) {
ent.setDept(deptService.findByAbr(DeptEnum.RTS.name()));
} else {
ent.setDept(deptService.findByAbr(DeptEnum.RTB.name()));
}
ent.setGrant(eForm.getGrant());
ent.setCountry(eForm.getCountry());
ent.setName(eForm.getName());
ent = service.create(ent);
newEntities.add(ent);
}
}
}
return newEntities;
}
EntityList is the POJO for my form. This POJO contains a list of EntityForm (+ a string for test purpose), which is a DTO for my database entity Entity.
EntityList POJO :
public class EntityList implements Serializable {
private static final long serialVersionUID = 6335318686899794229L;
private List<EntityForm> entities;
private String test;
public EntityList() {
super();
}
public EntityList(List<EntityForm> entities, String test) {
super();
this.entities = entities;
this.test = test;
}
public List<EntityForm> getEntities() {
return entities;
}
public void setEntities(List<EntityForm> entities) {
this.entities = entities;
}
public String getTest() {
return test;
}
public void setTest(String test) {
this.test = test;
}
}
I thought the problem came from a bad mapping between my list of entities in my form and my List<EntityForm> in my POJO, that's why I added a simple String to my POJO.
AngularJS side
Service :
app.factory("Entities", function($resource) {
return $resource("api/auth/entities", null,
{
createEntities: {method:'POST', url: "api/auth/entities/createEntities", params: { kind: '#kind' }, isArray:true}
});
})
Controller :
$scope.EntForm = {};
$scope.EntForm.entities = [];
$scope.EntForm.test = "myTest";
/* ... */
$scope.saveEnt= function() {
console.log($scope.EntForm);
Entities.createEntities($scope.EntForm,{kind:"RTS"},function(res) {
var msg = 'Entities created...';
ngToast.create(msg);
$location.path("/entities");
});
}
In my firefox console, I see that $scope.EntForm is correctly set (I have all my entity objects with the fields set, as well as the test string defined in the controller).
Result
All this code will display :
Creating entity for: RTS
Entity test = null
What am I doing wrong ?
Have you checked out the POST payload with Firefox developer tools, is your custom createEntities method working correctly?
(Would have added this as a comment, but unfortunately I don't yet have enough reputation for that.)
I had to remove the #RequestParam final String kind part from my Spring controller, and the param in AngularJS code.
To get the kind, I just added $scope.EntForm.kind = "theValueIWant" in my AngularJS controller.
I don't know if it's a good way to make it work in terms of good practice, but I get the #RequestBody content now.
I have the following object:
{
"id" : "sampleId";
foos : [{
"prop1":"value1",
"prop2":"value2"
},
{
"prop1":"value3",
"prop2":"value4"
}
]}
How can I get foos, where prop2 is value4? I'm using Spring data mongodb.
If you use spring data mongodb repositories, it can make your life easy. You will need a domain class. e.g.
public class Foo {
String prop1;
String prop2;
}
public class MyClass {
String id;
List<Foo> foos;
}
public interface MyClassRepository extends MongoRepository<MyClass, String> {
List<MyClass> findByFoosProp2(String prop2ValueToLookFor);
// assuming you can find multiple matches
}
Then simply call this method from your code
public clsss SomeBusinessClass {
#Autowired
private MyClassRepository repository;
public void mySomeBusinessMethod() {
List<MyClass> myObjects = repository.findByFoosProp2("value4");
}
}
This code will return a SampleBean with id sampleId wich will have only matching items in collection foos.
// Match one document with sampleId
Query q1 = new Query(where("id").is("sampleId"));
// match only foos with prop2 value value2
Query q2 = query(where("foos").elemMatch(where("prop2").is("value2))
BasicQuery query = new BasicQuery(q1.getQueryObject(), q2.getQueryObject());
SampleBean result = mongoTemplate.findOne(query, SampleBean.class)