KittyMac / Sextant

High performance JSONPath queries for Swift

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Is it possible to set value for jsonpaths

ilkerc opened this issue · comments

just as on com.jayway.jsonpath.DocumentContext.set(), are there any implementations for settings a value for given jsonpath ?

   /**
     * Set the value a the given path
     *
     * @param path      path to set
     * @param newValue  new value
     * @return a document context
     */
    DocumentContext set(JsonPath path, Object newValue);

There is not, but I can add it. Probably this weekend.

@KittyMac sounds great, looking forward.

@ilkerc v0.4.0 is up and now supports this.

@KittyMac works quite well thanks!. I did implement most of my use cases but the following;

let json = #"{"someValue": ["elem1", "elem2", "elem3"]}"#
let someValue = #"["elem4", "elem5"]"#
let newValue: String? = json.parsed { root in
    root?.query(map: "$.someValue", { _ in
        // ignore callback's jsonelement, return mine
        let newValue: JsonElement? = Spanker.parsed(string: someValue, { $0 })
        return newValue
    })
    
    return root?.description
}
print(newValue)

The problem is array of strings can't be parsed by Spanker

I think the problem is with the parser let newValue: JsonElement? = Spanker.parsed(string: someValue, { $0 })
I checked on callback; the returned JsonElement is not the same as the one on the callback.

I think this might be related with HalfHitch.using, or am I missing something?

This can be more easily implemented like this:

let oldJson = #"{"someValue": ["elem1", "elem2", "elem3"]}"#
let newJson: String? = oldJson.query(map: "$.someValue", {_ in
    return ["elem4", "elem5"]
} ) { root in
    return root.description
}
XCTAssertEqual(newJson, #"{"someValue":["elem4","elem5"]}"#)

The return value from your map callback is auto-converted to a JsonElement for you, so you can return any json-like-thing and it should handle it. This is more costly performance-wise (since it needs to use as? to determine what the thing is at runtime), but should be fine unless you discover it is too slow for your use case.

Spanker's performance comes from making a hierarchy of JsonElements who reference the memory in the original storage (like a bunch of SubStrings referencing a single String, these lightweight objects are cheap to make so long as they don't need to make copies). This is the reason for the .parsed() { root in ... }, as the source data is only guaranteed to be live inside of it.

If you wanted to do the above more performantly, then we want to move the creation of the JsonElement outside of the map. Like this:

let oldJson = #"{"someValue": ["elem1", "elem2", "elem3"]}"#
let replacementElement = JsonElement(unknown: ["elem4", "elem5"])
let newJson: String? = oldJson.query(map: "$.someValue", {_ in
    return replacementElement
} ) { root in
    return root.description
}
XCTAssertEqual(newJson, #"{"someValue":["elem4","elem5"]}"#)

If you really want to create the element from a string, you can indeed use Spanker like so:

let oldJson = #"{"someValue": ["elem1", "elem2", "elem3"]}"#
let replacementJson = #"["elem4", "elem5"]"#
let newJson: String? = replacementJson.parsed { replacementElement in
    return oldJson.query(map: "$.someValue", {_ in
        return replacementElement
    } ) { root in
        return root.description
    }
}
XCTAssertEqual(newJson, #"{"someValue":["elem4","elem5"]}"#)

@KittyMac these are very elegant solutions, especially initializing JsonElement with anything is very useful. Thanks!