use serde_json for JSON parsing
This commit is contained in:
parent
10a9f9f2ca
commit
2939ad9918
3 changed files with 51 additions and 49 deletions
|
@ -10,5 +10,5 @@ description = "A library for working with Wikidata in Rust"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = { version = "0.4.19", features = ["std", "serde"], default-features = false }
|
chrono = { version = "0.4.19", features = ["std", "serde"], default-features = false }
|
||||||
serde = { version = "1.0.126", features = ["derive"] }
|
serde = { version = "1.0.126", features = ["derive"] }
|
||||||
json = "0.12.4"
|
serde_json = "1.0.64"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
|
|
@ -2,6 +2,7 @@ use crate::ids::{consts, Fid, Lid, Pid, Qid, Sid};
|
||||||
use crate::text::{Lang, Text};
|
use crate::text::{Lang, Text};
|
||||||
use chrono::{DateTime, TimeZone, Utc};
|
use chrono::{DateTime, TimeZone, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
/// A Wikibase entity: this could be an entity, property, or lexeme.
|
/// A Wikibase entity: this could be an entity, property, or lexeme.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
@ -244,26 +245,30 @@ pub enum EntityError {
|
||||||
InvalidPrecision,
|
InvalidPrecision,
|
||||||
/// No rank was specified
|
/// No rank was specified
|
||||||
NoRank,
|
NoRank,
|
||||||
|
/// A number was out of bounds
|
||||||
|
NumberOutOfBounds,
|
||||||
|
/// No ID was found
|
||||||
|
NoId,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_json_string(mut json: json::JsonValue) -> Result<String, EntityError> {
|
fn get_json_string(json: Value) -> Result<String, EntityError> {
|
||||||
json.take_string().ok_or(EntityError::ExpectedString)
|
json.as_str().map(ToString::to_string).ok_or(EntityError::ExpectedString)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_wb_number(num: &json::JsonValue) -> Result<f64, EntityError> {
|
fn parse_wb_number(num: &Value) -> Result<f64, EntityError> {
|
||||||
// could be a string repersenting a number, or a number
|
match num {
|
||||||
if num.is_number() {
|
Value::Number(num) => num.as_f64().ok_or(EntityError::NumberOutOfBounds),
|
||||||
Ok(num.as_number().ok_or(EntityError::FloatParse)?.into())
|
Value::String(s) => {
|
||||||
} else {
|
match s.parse() {
|
||||||
let s = num.as_str().ok_or(EntityError::ExpectedNumberString)?;
|
Ok(x) => Ok(x),
|
||||||
match s.parse() {
|
Err(_) => Err(EntityError::FloatParse),
|
||||||
Ok(x) => Ok(x),
|
}
|
||||||
Err(_) => Err(EntityError::FloatParse),
|
|
||||||
}
|
}
|
||||||
|
_ => Err(EntityError::ExpectedNumberString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_get_as_qid(datavalue: &json::JsonValue) -> Result<Qid, EntityError> {
|
fn try_get_as_qid(datavalue: &Value) -> Result<Qid, EntityError> {
|
||||||
match datavalue
|
match datavalue
|
||||||
.as_str()
|
.as_str()
|
||||||
.ok_or(EntityError::ExpectedUriString)?
|
.ok_or(EntityError::ExpectedUriString)?
|
||||||
|
@ -277,8 +282,11 @@ fn try_get_as_qid(datavalue: &json::JsonValue) -> Result<Qid, EntityError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn take_prop(key: &'static str, claim: &mut json::JsonValue) -> json::JsonValue {
|
fn take_prop(key: &'static str, claim: &mut Value) -> Value {
|
||||||
claim.remove(key)
|
match claim.as_object_mut() {
|
||||||
|
Some(obj) => obj.remove(key).unwrap_or(Value::Null),
|
||||||
|
None => Value::Null,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_wb_time(time: &str) -> Result<chrono::DateTime<chrono::offset::Utc>, EntityError> {
|
fn parse_wb_time(time: &str) -> Result<chrono::DateTime<chrono::offset::Utc>, EntityError> {
|
||||||
|
@ -349,8 +357,8 @@ impl ClaimValueData {
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// If the `snak` does not correspond to a valid snak, then an error will be returned.
|
/// If the `snak` does not correspond to a valid snak, then an error will be returned.
|
||||||
pub fn parse_snak(mut snak: json::JsonValue) -> Result<Self, EntityError> {
|
pub fn parse_snak(mut snak: Value) -> Result<Self, EntityError> {
|
||||||
let mut datavalue: json::JsonValue = take_prop("datavalue", &mut snak);
|
let mut datavalue: Value = take_prop("datavalue", &mut snak);
|
||||||
let datatype: &str = &get_json_string(take_prop("datatype", &mut snak))?;
|
let datatype: &str = &get_json_string(take_prop("datatype", &mut snak))?;
|
||||||
let snaktype: &str = &get_json_string(take_prop("snaktype", &mut snak))?;
|
let snaktype: &str = &get_json_string(take_prop("snaktype", &mut snak))?;
|
||||||
match snaktype {
|
match snaktype {
|
||||||
|
@ -360,14 +368,16 @@ impl ClaimValueData {
|
||||||
_ => return Err(EntityError::InvalidSnaktype),
|
_ => return Err(EntityError::InvalidSnaktype),
|
||||||
};
|
};
|
||||||
let type_str = take_prop("type", &mut datavalue)
|
let type_str = take_prop("type", &mut datavalue)
|
||||||
.take_string()
|
.as_str()
|
||||||
.ok_or(EntityError::InvalidSnaktype)?;
|
.ok_or(EntityError::InvalidSnaktype)?
|
||||||
|
.to_string();
|
||||||
let mut value = take_prop("value", &mut datavalue);
|
let mut value = take_prop("value", &mut datavalue);
|
||||||
match &type_str[..] {
|
match &type_str[..] {
|
||||||
"string" => {
|
"string" => {
|
||||||
let s = value
|
let s = value
|
||||||
.take_string()
|
.as_str()
|
||||||
.ok_or(EntityError::ExpectedStringDatatype)?;
|
.ok_or(EntityError::ExpectedStringDatatype)?
|
||||||
|
.to_string();
|
||||||
match datatype {
|
match datatype {
|
||||||
"string" => Ok(ClaimValueData::String(s)),
|
"string" => Ok(ClaimValueData::String(s)),
|
||||||
"commonsMedia" => Ok(ClaimValueData::CommonsMedia(s)),
|
"commonsMedia" => Ok(ClaimValueData::CommonsMedia(s)),
|
||||||
|
@ -450,9 +460,8 @@ impl ClaimValueData {
|
||||||
impl ClaimValue {
|
impl ClaimValue {
|
||||||
/// Try to parse a JSON claim to a claim value.
|
/// Try to parse a JSON claim to a claim value.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn get_prop_from_snak(mut claim: json::JsonValue, skip_id: bool) -> Option<ClaimValue> {
|
pub fn get_prop_from_snak(mut claim: Value, skip_id: bool) -> Option<ClaimValue> {
|
||||||
let claim_str = take_prop("rank", &mut claim).take_string()?;
|
let rank = match take_prop("rank", &mut claim).as_str()? {
|
||||||
let rank = match &claim_str[..] {
|
|
||||||
"deprecated" => {
|
"deprecated" => {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
@ -462,21 +471,14 @@ impl ClaimValue {
|
||||||
};
|
};
|
||||||
let mainsnak = take_prop("mainsnak", &mut claim);
|
let mainsnak = take_prop("mainsnak", &mut claim);
|
||||||
let data = ClaimValueData::parse_snak(mainsnak).ok()?;
|
let data = ClaimValueData::parse_snak(mainsnak).ok()?;
|
||||||
let references_json = take_prop("references", &mut claim);
|
let references = if let Some(arr) = take_prop("references", &mut claim).as_array() {
|
||||||
let references = if references_json.is_array() {
|
let mut v: Vec<ReferenceGroup> = Vec::with_capacity(arr.len());
|
||||||
let mut v: Vec<ReferenceGroup> = Vec::with_capacity(references_json.len());
|
for reference_group in arr {
|
||||||
let mut references_vec = if let json::JsonValue::Array(a) = references_json {
|
let reference_group = reference_group.as_object()?;
|
||||||
a
|
let mut claims = Vec::with_capacity(reference_group["snaks"].as_array()?.len());
|
||||||
} else {
|
let snaks = reference_group["snaks"].as_object()?;
|
||||||
return None;
|
for (pid, snak_group) in snaks.iter() {
|
||||||
};
|
for snak in snak_group.as_array()?.iter() {
|
||||||
for mut reference_group in references_vec.drain(..) {
|
|
||||||
let mut claims = Vec::with_capacity(reference_group["snaks"].len());
|
|
||||||
let snaks = take_prop("snaks", &mut reference_group);
|
|
||||||
let mut entries: Vec<(&str, &json::JsonValue)> = snaks.entries().collect();
|
|
||||||
for (pid, snak_group) in entries.drain(..) {
|
|
||||||
let mut members: Vec<&json::JsonValue> = snak_group.members().collect();
|
|
||||||
for snak in members.drain(..) {
|
|
||||||
// clone, meh
|
// clone, meh
|
||||||
let owned_snak = snak.clone().take();
|
let owned_snak = snak.clone().take();
|
||||||
if let Ok(x) = ClaimValueData::parse_snak(owned_snak) {
|
if let Ok(x) = ClaimValueData::parse_snak(owned_snak) {
|
||||||
|
@ -487,17 +489,17 @@ impl ClaimValue {
|
||||||
v.push(ReferenceGroup { claims });
|
v.push(ReferenceGroup { claims });
|
||||||
}
|
}
|
||||||
v
|
v
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
Vec::new()
|
||||||
};
|
};
|
||||||
let qualifiers_json = take_prop("qualifiers", &mut claim);
|
let qualifiers_json = take_prop("qualifiers", &mut claim);
|
||||||
let qualifiers = if qualifiers_json.is_object() {
|
let qualifiers = if qualifiers_json.is_object() {
|
||||||
let mut v: Vec<(Pid, ClaimValueData)> = vec![];
|
let mut v: Vec<(Pid, ClaimValueData)> = vec![];
|
||||||
let mut entries: Vec<(&str, &json::JsonValue)> = qualifiers_json.entries().collect();
|
for (pid, claim_array_json) in qualifiers_json.as_object()?.iter() {
|
||||||
for (pid, claim_array_json) in entries.drain(..) {
|
|
||||||
// yep it's a clone, meh
|
// yep it's a clone, meh
|
||||||
let mut claim_array =
|
let mut claim_array =
|
||||||
if let json::JsonValue::Array(x) = claim_array_json.clone().take() {
|
if let Value::Array(x) = claim_array_json.clone().take() {
|
||||||
x
|
x
|
||||||
} else {
|
} else {
|
||||||
return None;
|
return None;
|
||||||
|
@ -518,8 +520,8 @@ impl ClaimValue {
|
||||||
String::new()
|
String::new()
|
||||||
} else {
|
} else {
|
||||||
take_prop("id", &mut claim)
|
take_prop("id", &mut claim)
|
||||||
.take_string()
|
.as_str()?
|
||||||
.expect("No id on snak")
|
.to_string()
|
||||||
},
|
},
|
||||||
data,
|
data,
|
||||||
references,
|
references,
|
||||||
|
@ -564,7 +566,7 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn as_qid_test() {
|
fn as_qid_test() {
|
||||||
let qid =
|
let qid =
|
||||||
try_get_as_qid(&json::parse(r#""http://www.wikidata.org/entity/Q1234567""#).unwrap());
|
try_get_as_qid(&serde_json::from_str(r#""http://www.wikidata.org/entity/Q1234567""#).unwrap());
|
||||||
assert_eq!(qid, Ok(Qid(1234567)));
|
assert_eq!(qid, Ok(Qid(1234567)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn simple_snak_from_json() {
|
fn simple_snak_from_json() {
|
||||||
let j = json::parse(include_str!("../items/Q106975887.json")).unwrap();
|
let j: serde_json:: Value = serde_json::from_str(include_str!("../items/Q106975887.json")).unwrap();
|
||||||
let snak = &j["entities"]["Q106975887"]["claims"]["P31"][0]["mainsnak"];
|
let snak = &j["entities"]["Q106975887"]["claims"]["P31"][0]["mainsnak"];
|
||||||
println!("{:?}", snak);
|
println!("{:?}", snak);
|
||||||
wikidata::ClaimValueData::parse_snak(snak.clone()).unwrap();
|
wikidata::ClaimValueData::parse_snak(snak.clone()).unwrap();
|
||||||
|
@ -11,7 +11,7 @@ fn simple_snak_from_json() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn complex_snak_from_json() {
|
fn complex_snak_from_json() {
|
||||||
let j = json::parse(include_str!("../items/Q42.json")).unwrap();
|
let j: serde_json:: Value = serde_json::from_str(include_str!("../items/Q42.json")).unwrap();
|
||||||
let snak = &j["entities"]["Q42"]["claims"]["P18"][0]["mainsnak"];
|
let snak = &j["entities"]["Q42"]["claims"]["P18"][0]["mainsnak"];
|
||||||
println!("{:?}", snak);
|
println!("{:?}", snak);
|
||||||
wikidata::ClaimValueData::parse_snak(snak.clone()).unwrap();
|
wikidata::ClaimValueData::parse_snak(snak.clone()).unwrap();
|
||||||
|
@ -19,7 +19,7 @@ fn complex_snak_from_json() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn other_complex_snak_from_json() {
|
fn other_complex_snak_from_json() {
|
||||||
let j = json::parse(include_str!("../items/Q1.json")).unwrap();
|
let j: serde_json:: Value = serde_json::from_str(include_str!("../items/Q1.json")).unwrap();
|
||||||
let snak = &j["entities"]["Q1"]["claims"]["P793"][0]["mainsnak"];
|
let snak = &j["entities"]["Q1"]["claims"]["P793"][0]["mainsnak"];
|
||||||
println!("{:?}", snak);
|
println!("{:?}", snak);
|
||||||
wikidata::ClaimValueData::parse_snak(snak.clone()).unwrap();
|
wikidata::ClaimValueData::parse_snak(snak.clone()).unwrap();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue