Comments (2)
Thanks for putting this together, and apologies for the delay.
And if people want to stick to immutable values, they can always just declare their JSON values using let and that’s it.
Yes, that makes sense. The choice of immutability is shifted to the consumer – which I think is fine.
Incompatible JSON Types
And a reasonable default behaviour could be to simply change self to the expected JSON value type
This is a tough one. It would follow the dynamically typed origins of JSON, Javascript. That said, it does make for a tricky API. To the caller, the variable type, JSON
, does not change but the internal 'type' (i.e. enum case) changes.
I can see how this would make complete sense to the caller:
var foo: JSON = 1 // foo is .number
foo = JSON("hello") // foo is now .string
the case has changed from .number
to .string
– which is reasonable.
However, here:
var foo: JSON = 1 // foo is .number
foo.a = "b" // foo is now .object {"a": "b"}
the type is being "upgraded" from .number
to .object
as a result of a subscript assignment.
My view that we should not allow the "upgrading" of type as a result of a subscript assignment if the underlying case does not logically support it.
Out-Of-Bound Array Access
Since it’s syntactically valid to do JSON([])[10] = 1, what does it mean? Should it be a hard out-of-bounds error like with ordinary Swift array accesss, a no-op, or should we grow the array automatically? I’m against a hard error, since the value often comes from an external source and it would be harsh to crash an app just because an external API source forgot to include an expected value in a response.
Growing the array automatically would be a bit closer to how arrays behave in JavaScript. But I think that’s a false analogy anyway, so I would tend to pick the remaining option and not do anything – except for converting the value to array if needed, see remark below about removing keys from a dictionary.
With help from my Android colleague, @LordCheddar , we inspected what similar libraries do.
- Adds more storage (grows the array with json nulls)
fun jsonArrayTest() {
val temp = JSONObject()
temp.put("array", JSONArray().put(5, "test"))
val array = temp.getJSONArray("array")
(0 until array.length()).forEach {
Log.e("json", "index $it - ${array.opt(it)}")
}
}
JSON
2019-06-24 13:31:31.780 11671-11671/com.ydangleapps.jsontest E/json: index 0 - null
2019-06-24 13:31:31.780 11671-11671/com.ydangleapps.jsontest E/json: index 1 - null
2019-06-24 13:31:31.780 11671-11671/com.ydangleapps.jsontest E/json: index 2 - null
2019-06-24 13:31:31.780 11671-11671/com.ydangleapps.jsontest E/json: index 3 - null
2019-06-24 13:31:31.780 11671-11671/com.ydangleapps.jsontest E/json: index 4 - null
2019-06-24 13:31:31.780 11671-11671/com.ydangleapps.jsontest E/json: index 5 - test
- Runtime crash
fun gsonArrayTest() {
val temp = JsonObject()
val jarray = JsonArray(6)
jarray.set(5, JsonPrimitive("test"))
temp.add("array", jarray)
val array = temp.get("array").asJsonArray
(0 until array.size()).forEach {
Log.e("gson", "index $it - ${array.get(it).asString}")
}
}
GSON - java.lang.IndexOutOfBoundsException: Index: 5, Size: 0
I am not sure what the best approach is here, however as you mentioned, I also think the runtime exception is not a good choice.
Removing Keys From Dictionaries
In this context, and from what I can tell (again no expert), it comes down to how the language's nil
/null
concept translates to JSON's null
.
Setting an object's value to language's nil
/null
will either:
- remove the key pair, or
- replace the value with JSON's
null
.
- Assigning language's
null
to a dictionary value will remove the key pair. - In order to assign
null
to a value, the JSON type'snull
value must be used.
fun jsonArrayTest() {
val temp = JSONObject()
temp.put("array", JSONArray().put(5, "test"))
val array = temp.getJSONArray("array")
(0 until array.length()).forEach {
Log.e("json", "index $it - ${array.opt(it)}")
}
}
2019-06-24 13:38:12.843 13524-13524/com.ydangleapps.jsontest E/json: {"array":["test"]}
2019-06-24 13:38:12.843 13524-13524/com.ydangleapps.jsontest E/json: {}
- Assigning language's
null
to a dictionary value will replace the value withnull
. - Removing the key-pair entirely must be done using
remove()
.
fun gsonTypeNullTest() {
val temp = JsonObject()
val jarray = JsonArray(6)
jarray.add(JsonPrimitive("test"))
temp.add("array", jarray)
Log.e("gson", temp.toString())
temp.add("array", null)
Log.e("gson", temp.toString())
temp.remove("array")
Log.e("gson", temp.toString())
}
2019-06-24 13:38:12.860 13524-13524/com.ydangleapps.jsontest E/gson: {"array":["test"]}
2019-06-24 13:38:12.860 13524-13524/com.ydangleapps.jsontest E/gson: {"array":null}
2019-06-24 13:38:12.861 13524-13524/com.ydangleapps.jsontest E/gson: {}
In the proposed method, we are using the Swift language's nil
to remove key-pairs:
The syntax is obvious again:
json["foo"] = nil
json.foo = nil
I see one inconsistency here, currently initialisation using Swift's nil
produces JSON.null
:
let foo = JSON(nil) // .null
and the proposal for 'Removing Items From Arrays' treats a nil
assignment as JSON.null
assignment.
Given this, would it make more sense to add an explicit remove()
function (similar to GSON) which removes key-pairs? So given var foo: JSON = ["bar" : true]
these two assignments would be equivalent:
foo.bar = .null
foo.bar = nil
Do these comments help narrow the solution space?
from generic-json-swift.
In-Place Mutation
This is how in-place mutation could look from the caller’s perspective:
// Note `var` instead of `let`
var json: JSON = …
// Writable key subscript
json["foo"] = bar
// Writable index subscript
json[0] = baz
// Dynamic member subscript
json.foo = bar
// In-place merge
json.merge(["foo": "bar"])
Incompatible JSON Types
One catch is what should be done when the JSON value is not compatible with the change, ie. when someone tries to change the foo
key on a JSON float or write to the first index of a JSON dictionary:
var foo: JSON = 1
foo.a = "b" // now `foo` is what?
We had some discussion around that in #25 and it seems that there are three options – design the types to make these operations impossible, signal an error, or use a reasonable default behaviour (possibly including doing nothing). Changing the types seems a little heavy-handed and returning an error would be hard to do (how do we return an error when using an invalid subscript write?), so it seems that the last option is best. And a reasonable default behaviour could be to simply change self
to the expected JSON value type:
JSON(1).a = "b" // {"a": "b"}
JSON(1)[0] = "x" // ["x"]
I’m not really happy about it, since it makes it too easy for people to shoot themselves in the foot, but I can’t think of a better solution. And at least it’s consistent with how merging works at the moment.
Out-Of-Bound Array Access
Since it’s syntactically valid to do JSON([])[10] = 1
, what does it mean? Should it be a hard out-of-bounds error like with ordinary Swift array accesss, a no-op, or should we grow the array automatically? I’m against a hard error, since the value often comes from an external source and it would be harsh to crash an app just because an external API source forgot to include an expected value in a response.
Growing the array automatically would be a bit closer to how arrays behave in JavaScript. But I think that’s a false analogy anyway, so I would tend to pick the remaining option and not do anything – except for converting the value to array if needed, see remark below about removing keys from a dictionary.
Removing Keys From Dictionaries
The syntax is obvious again:
json["foo"] = nil
json.foo = nil
It’s not obvious what whould be done when json
is not a dictionary. One option is to do nothing, one option is to convert the value to a dictionary first:
JSON(1).foo = nil // 1
JSON(2).foo = nil // {}
The first choice is consistent with how key reading from non-dictionary values works, the second choice is similar to how key writing works. I’m in favour of the second one.
Removing Items From Arrays
The json[0] = nil
syntax really means json[0] = JSON.null
. Writing to an out-of-bound index should be ignored, but the value would be converted to an array if not an array already, just as in a dictionary.
I have left out the immutable update API. I think we can get to that later, as it will only be a thin wrapper above the mutable one (create a mutable copy, run a mutating change, return). @ezamagni, @cjmconie, you both wanted mutation support, how does this look to you? Is anything missing, inconsistent, surprising, wrong?
from generic-json-swift.
Related Issues (16)
- Publish new version of the pod? HOT 2
- Add support for Swift Package Manager
- Automate release process HOT 1
- Add Linux support
- Switch to a cross-platform framework
- Usage after decoding HOT 6
- Initialisation using `nil` or `NSNull` HOT 5
- Tags Are Not SPM-Compliant HOT 3
- JSON initialisation using variables of primitive type HOT 2
- NSNumber Initialization HOT 3
- How to use JSON in Core Data HOT 7
- Provide `Decoder` to turn `JSON` into `Decodable` HOT 2
- Loop through JSON keys?
- Improve querying using dynamic member lookup? HOT 1
- Merging JSON values HOT 5
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from generic-json-swift.