Change Pydantic's inherited BaseModel's attribute precedence in JSON schema - json

I'm using pydantic 1.6.1 and fastapi 0.61.1 with Python 3.8.3. Here's how I've tried defining the models:
class UserBase(BaseModel):
name: str
class UserCreate(UserBase):
password: str
class UserInfo(UserBase):
id: str
group: Optional[GroupInfo] = None
The issue I'm having with this setup is that the schema is built such that the name attribute is on top of the remaining attributes like so - in this case, while using UserInfo as the endpoint's response_model:
[
{
"name": "string",
"id": "string",
"group": {
"name": "string"
}
}
]
Yet I'd like to be able to set them up like this:
[
{
"id": "string",
"name": "string",
"group": {
"name": "string"
}
}
]
Is there a way I can manually set a custom order for the attributes in the JSON response's schema?

As I mentioned in my comment, it doesn't really matter the order of the JSON, but when it comes to schema generation, it can be helpful.
You need to decouple the id field from UserInfo model as
class UserID(BaseModel):
id: str
class UserInfo(UserBase, UserID): # `UserID` should be second
group: Optional[GroupInfo] = None
This will generate the following JSON schema,
{
"id": "string",
"name": "string",
"group": {
"name": "string"
}
}
Bit more ugly solution (IMO)
patch the .__fields__ as
class UserBase(BaseModel):
name: str
class UserCreate(UserBase):
password: str
class UserInfo(UserBase):
id: str
group: Optional[GroupInfo] = None
fields = UserInfo.__fields__.copy()
UserInfo.__fields__ = {"id": fields.pop("id"), **fields}

Related

RealmList<String> as a JSON Schema - Mongo DB Realm

I have a simple model class from which I need to generate the schema on Mongo DB Atlas. But I'm having troubles when it comes to defining RealmList<String> inside a JSON schema. If I insert "array" as a bsonType, I get an error. What should I write instead?
Model class:
class Note : RealmObject {
#PrimaryKey
var _id: ObjectId = ObjectId.create()
var title: String = ""
var description: String = ""
var images: RealmList<String> = realmListOf()
var date: RealmInstant = RealmInstant.from(System.currentTimeMillis(),0)
}
Current Schema:
{
"bsonType": "object",
"properties": {
"_id": {
"bsonType": "objectId"
},
"title": {
"bsonType": "string"
},
"description": {
"bsonType": "string"
},
"images": {
"bsonType": "array"
},
"date": {
"bsonType": "date"
}
},
"required": [
"_id",
"title",
"description",
"images",
"date"
],
"title": "Note"
}
I am not sure which mode you're using but if you're in development mode, when you add an object in the SDK, the server will automatically generate a matching object, as long as the changes are additive, like adding a new object or property
In the queston, the 'images' bson definition looks incomplete
"images": {
"bsonType": "array"
},
While it is an array, it's an array of strings so I believe it should look more like this
"images": {
"bsonType": "array",
"items": {
"bsonType": "string"
}
}
Where the type of items is defined as a string

How do I assign a value in JSON that becomes an enum string value in TypeScript?

I'm trying to add sample data for my unit tests and I'm getting an assignment error when I'm trying to read in the JSON file related to the enum assignment.
types.ts
export enum TestType {
TYPE1 = "type1",
TYPE2 = "type2",
TYPE3 = "type 3"
}
export interface SampleDataType {
id: string;
name: string;
dataType?: TestType;
}
exampleData.json
[
{
"id": "1",
"name": "first",
"dataType": "type1"
},
{
"id": "2",
"name": "second",
"dataType": "type2"
},
{
"id": "3",
"name": "third",
"dataType": "type3"
}
]
unitTest.ts
import * as exampleData from "../../components/__mocks__/test-data/exampleData.json";
//...
const testItems: SampleDataType[] = exampleData;
I'm getting the following error:
I've also tried using "TYPE1" as the sample data value, but no luck. Is there a way to assign the value in json and have it read in the unit test?

How to read nested array elements from JSON?

I need to parse a JSON with nested array elements and extract the values.
I am not sure how to use the nested array to set the value of an attribute in output JSON.
This is the input:
[{
"name": "book1",
"id": 18789,
"locations": [{
"state": "mystate",
"phone": 8877887700
}, {
"state": "mystate1",
"phone": 8877887701
}]
},
{
"name": "book2",
"id": 18781,
"locations": [{
"state": "mystate3",
"phone": 8877887711
}, {
"state": "mystate4",
"phone": 8877887702
}]
}]
And this is the expected output:
{
"name": ["book1", "book2"],
"id": ["18789", "18781"],
"states": [
["mystate", "mystate"],
["mystate3", "mystate4"]
]
}
I am trying to use the following JSLT expression:
{
"name" : [for (.)
let s = string(.name)
$s],
"id": [for (.)
let s = string(.id)
$s],
"states": [for (.)
let s = string(.locations)
$s]
}
But I am not sure how to set the states in this case so that I have the value of state in the output.
A solution using JQ or JSONPath may also help.
With JQ it'd be easier than that.
{
name: map(.name),
id: map(.id),
states: map(.locations | map(.state))
}
Online demo
In JSLT you can implement it like this:
{
"name" : [for (.) .name],
"id": [for (.) .id],
"states": flatten([for (.) [for (.locations) .state]])
}
The states key is a bit awkward to implement, as you see. I have thought of making it possible to let path expressions traverse arrays, and if we add that to the language it could be implemented like this:
{
"name" : .[].name,
"id": .[].id,
"states": .[].locations.[].state
}

Kotlin Jackson generation objects from JSON

Please help! I'm trying to generate object from JSON with jackson kotlin module. Here is json source:
{
"name": "row",
"type": "layout",
"subviews": [{
"type": "horizontal",
"subviews": [{
"type": "image",
"icon": "ic_no_photo",
"styles": {
"view": {
"gravity": "center"
}
}
}, {
"type": "vertical",
"subviews": [{
"type": "text",
"fields": {
"text": "Some text 1"
}
}, {
"type": "text",
"fields": {
"text": "Some text 2"
}
}]
}, {
"type": "vertical",
"subviews": [{
"type": "text",
"fields": {
"text": "Some text 3"
}
}, {
"type": "text",
"fields": {
"text": "Some text 4"
}
}]
}, {
"type": "vertical",
"subviews": [{
"type": "image",
"icon": "ic_no_photo"
}, {
"type": "text",
"fields": {
"text": "Some text 5"
}
}]
}]
}]
}
I'm trying to generate instance of Skeleton class.
data class Skeleton (val type : String,
val name: String,
val icon: String,
val fields: List<Field>,
val styles: Map<String, Map<String, Any>>,
val subviews : List<Skeleton>)
data class Field (val type: String, val value: Any)
As you can see, Skeleton object can have other Skeleton objects inside (and these objects can have other Skeleton objects inside too), also Skeleton can have List of Field objects
val mapper = jacksonObjectMapper()
val skeleton: Skeleton = mapper.readValue(File(file))
This code ends with exception:
com.fasterxml.jackson.databind.JsonMappingException: Instantiation of [simple type, class com.uibuilder.controllers.parser.Skeleton] value failed (java.lang.IllegalArgumentException): Parameter specified as non-null is null: method com.uibuilder.controllers.parser.Skeleton.<init>, parameter name
at [Source: docs\layout.txt; line: 14, column: 3] (through reference chain: com.uibuilder.controllers.parser.Skeleton["subviews"]->java.util.ArrayList[0]->com.uibuilder.controllers.parser.Skeleton["subviews"]->java.util.ArrayList[0])
There are several issues I found about your mapping that prevent Jackson from reading the value from JSON:
Skeleton class has not-null constructor parameters (e.g. val type: String, not String?), and Jackson passes null to them if the value for those parameters is missing in JSON. This is what causes the exception you mentioned:
Parameter specified as non-null is null: method com.uibuilder.controllers.parser.Skeleton.<init>, parameter name
To avoid it, you should mark the parameters that might might have values missing as nullable (all of the parameters in your case):
data class Skeleton(val type: String?,
val name: String?,
val icon: String?,
val fields: List<Field>?,
val styles: Map<String, Map<String, Any>>?,
val subviews : List<Skeleton>?)
fields in Skeleton has type List<Field>, but in JSON it's represented by a single object, not by an array. The fix would be to change the fields parameter type to Field?:
data class Skeleton(...
val fields: Field?,
...)
Also, Field class in your code doesn't match the objects in JSON:
"fields": {
"text": "Some text 1"
}
You should change Field class as well, so that it has text property:
data class Field(val text: String)
After I made the changes I listed, Jackson could successfully read the JSON in question.
See also: "Null Safety" in Kotlin reference.

Groovy JSONBuilder issues

I'm trying to use JsonBuilder with Groovy to dynamically generate JSON. I want to create a JSON block like:
{
"type": {
"__type": "urn",
"value": "myCustomValue1"
},
"urn": {
"__type": "urn",
"value": "myCustomValue2"
},
"date": {
"epoch": 1265662800000,
"str": "2010-02-08T21:00:00Z"
},
"metadata": [{
"ratings": [{
"rating": "NR",
"scheme": "eirin",
"_type": {
"__type": "urn",
"value": "myCustomValue3"
}
}],
"creators": [Jim, Bob, Joe]
}]
}
I've written:
def addUrn(parent, type, urnVal) {
parent."$type" {
__type "urn"
"value" urnVal
}
}
String getEpisode(String myCustomVal1, String myCustomVal2, String myCustomVal3) {
def builder = new groovy.json.JsonBuilder()
def root = builder {
addUrn(builder, "type", myCustomVal1)
addUrn(builder, "urn", "some:urn:$myCustomVal2")
"date" {
epoch 1265662800000
str "2010-02-08T21:00:00Z"
}
"metadata" ({
ratings ({
rating "G"
scheme "eirin"
addUrn(builder, "_type", "$myCustomVal3")
})
creators "Jim", "Bob", "Joe"
})
}
return root.toString();
}
But I've run into the following issues:
Whenever I call addUrn, nothing is returned in the string. Am I misunderstanding how to use methods in Groovy?
None of the values are encapsulated in double (or single) quotes in the returned string.
Anytime I use a {, I get a '_getEpisode_closure2_closure2#(insert hex)' in the returned value.
Is there something wrong with my syntax? Or can someone point me to some example/tutorial that uses methods and/or examples beyond simple values (e.g. nested values within arrays).
NOTE: This is a watered down example, but I tried to maintain the complexity around the areas that were giving me issues.
You have to use delegate in addUrn method instead of
passing the builder on which you are working.
It is because you are doing a toSting() or toPrettyString() on root instead of builder.
Solved if #2 is followed.
Sample:
def builder = new groovy.json.JsonBuilder()
def root = builder {
name "Devin"
data {
type "Test"
note "Dummy"
}
addUrn(delegate, "gender", "male")
addUrn(delegate, "zip", "43230")
}
def addUrn(parent, type, urnVal) {
parent."$type" {
__type "urn"
"value" urnVal
}
}
println builder.toPrettyString()
Output:-
{
"name": "Devin",
"data": {
"type": "Test",
"note": "Dummy"
},
"gender": {
"__type": "urn",
"value": "male"
},
"zip": {
"__type": "urn",
"value": "43230"
}
}